Compare commits

...

162 Commits

Author SHA1 Message Date
George Sokianos 1abca0d4c7 Fixed the color problem of the editor 2021-12-19 22:24:44 +00:00
George Sokianos 3a5cadc116 First changes and new makefile to have it compile on OS4 2021-12-14 22:09:51 +00:00
rxi 38bd9b3326
Merge pull request #233 from takase1121/update_stb
update stb_truetype
2021-01-09 16:02:57 +00:00
takase1121 efa73e1ed1 update stb_truetype 2020-12-28 13:58:20 +08:00
rxi 806f0e39e3 Added support for `.pyw` files in language_python plugin 2020-12-19 10:19:37 +00:00
rxi 79c4f9fcae Updated README.md for build.bat addition 2020-10-03 13:49:14 +01:00
rxi b2ddc140d6 Added build.bat 2020-10-03 13:38:04 +01:00
rxi de5cb4fa52 Improved behaviour of `doc:join-lines` when joining to empty line 2020-09-27 15:03:53 +01:00
rxi 878c94a334 Changed rencache to store font tab_width with font command
Fixes bug where text would be drawn/cached wrongly if more than one tab_width
was used in a given frame
2020-09-05 15:09:54 +01:00
rxi 2caa7f182d Added `doc/usage.md`
Resolves #158
Resolves #103
2020-07-19 14:20:03 +01:00
rxi 91c43dc01e Version 1.11 2020-07-05 10:56:39 +01:00
rxi 99831bbc29 Added lua5.4 attribute support to `language_lua` 2020-07-01 09:26:41 +01:00
rxi 87532a4b3a Version 1.10 2020-06-28 14:40:07 +01:00
rxi 094cf0cc2c Fixed shift+click select behavior 2020-06-26 10:44:56 +01:00
rxi 53d555b362 Added support for mouse double/triple click+drag selection
Resolves #159
Resolves #161
2020-06-25 13:41:46 +01:00
rxi 11df722162 Version 1.09 2020-06-21 19:38:42 +01:00
rxi ae48049695 Changed `trimwhitespace` to never cause caret to reposition 2020-06-19 12:09:00 +01:00
rxi 6ec8fc5616
Create FUNDING.yml 2020-06-18 16:48:05 +01:00
rxi 1db1f0bceb Version 1.08 2020-06-14 13:33:23 +01:00
rxi 1a82fd2b92 Added `-fno-strict-aliasing` to build script 2020-06-13 13:35:36 +01:00
rxi 7517d0ef55 Changed EXEDIR to be used as default project dir 2020-06-13 08:56:13 +01:00
rxi 95b70b1b16 Revert "Removed __APPLE__ `#ifdef` from main.c"
This reverts commit 6a7e214d1c.
2020-06-11 15:05:39 +01:00
rxi ba6c14846b Added file-header pattern support to `syntax` 2020-06-08 16:11:22 +01:00
rxi db471c0554 Version 1.07 2020-06-07 14:02:45 +01:00
rxi 6a7e214d1c Removed __APPLE__ `#ifdef` from main.c 2020-06-07 08:57:57 +01:00
rxi 877d940c0e language_js improvements 2020-06-05 22:38:06 +01:00
rxi bd0644a5bb Added resetting of selection on intermediate find-text failure 2020-06-04 14:04:46 +01:00
rxi 3569abcb53
Merge pull request #133 from 6r1d/master
Fix for keypad enter issue
2020-06-04 13:17:33 +01:00
rxi dc766a644f
Merge pull request #137 from dslul/patch-1
Don't tell the system to disable compositing under X11
2020-06-03 14:34:50 +01:00
Daniele Laudani 95ee03fb37
move BYPASS_COMPOSITOR near other SetHint 2020-06-03 15:05:55 +02:00
Daniele Laudani 7aa462e43d
Don't tell the system to disable compositing under X11
Fixes #123
2020-06-03 14:38:44 +02:00
rxi 18de4552e2 Made tab's text left-aligned if wider than the tab 2020-06-03 13:34:10 +01:00
rxi 4b167e86c6 Fixed bug in Highlighter.invalidate() when setting first_invalid_line
The value should not be updated if the current first_invalid_line is less than
the new invalid line index
2020-06-02 22:50:03 +01:00
Victor Gridnevsky a6f52197d0 Fixes keypad enter issue (#131) 2020-06-02 13:26:16 +03:00
rxi f00d5d55df Version 1.06 2020-05-31 16:53:53 +01:00
rxi 508b6fb73a Improved RootView's EmptyView 2020-05-30 14:58:31 +01:00
rxi db8c5ea2aa Renamed core:command/file-finder => core:find-command/file 2020-05-30 09:11:42 +01:00
rxi 7fbefe40d5 Made `system.set_window_title` only be called on title change 2020-05-30 08:53:48 +01:00
rxi cc58fcc35b Changed summer color theme's caret color 2020-05-29 17:19:27 +01:00
rxi b96609b7b8 Removed redundant __APPLE__ case in get_scale() 2020-05-29 09:33:42 +01:00
rxi 1b2fda2825 Changed block movement to mimic word movement 2020-05-28 13:55:25 +01:00
rxi 9c652086e8 Improved behaviour of and renamed `translate.next|previous_word_boundary` 2020-05-28 11:57:53 +01:00
rxi 74755f5b4a Simplified implementation of `core.temp_filename()` 2020-05-27 11:38:42 +01:00
rxi e7cf551e22 Changed EmptyView text from `empty` to `lite` 2020-05-26 10:33:07 +01:00
rxi 064b6d0b95 Fixed changing of cwd and loading of commandline files
the current-working-directory is now set at the start of `core.init` after the
absolute path for all filename arguments have been resolved
2020-05-26 10:26:20 +01:00
rxi 257b9ab753 Added `core.temp_filename()` 2020-05-25 08:58:12 +01:00
rxi 61a2a2c4e5 Version 1.05 2020-05-24 13:52:10 +01:00
rxi c2d27ab3f7 Removed `core.project_dir` 2020-05-24 13:50:32 +01:00
rxi 82e33dd2de Moved event-waiting when not focused to after run_threads() 2020-05-24 13:43:34 +01:00
rxi 946c125fd4 Changed `core.redraw` to be set to `true` by default
As the window isn't created until the first frame is drawn this is required to
assure the window is ever shown without relying on the assumption that some
other part of the program would have set this to true
2020-05-24 08:04:47 +01:00
rxi 61092fbb99 Changed fuzzy matching to favour matching case 2020-05-23 15:08:38 +01:00
rxi 28b1844a8b Added support for dropping a folder onto the window 2020-05-23 11:31:08 +01:00
rxi e45b3e2bc0 Minor renaming in rootview 2020-05-23 09:40:42 +01:00
rxi e6a2770e2e Merge branch 'master' of https://github.com/rxi/lite 2020-05-22 18:59:59 +01:00
rxi 71fb50ece0
Merge pull request #111 from waywardmonkeys/fix-tmpnam-warning
Enable LUA_USE_POSIX, fix tmpnam warning on macOS.
2020-05-22 18:59:42 +01:00
rxi e7320c2291 Made RootView:open_doc() try to use previous node if current node is locked 2020-05-22 15:50:27 +01:00
rxi 35b642d434 Added rencache invalidation on window-exposed event
Fixes #63
2020-05-22 09:00:48 +01:00
rxi 35ce3d32a9 Fixed string quoting on windows in `system.exec()` 2020-05-22 08:11:05 +01:00
Bruce Mitchener c9f798a07b Enable LUA_USE_POSIX, fix tmpnam warning on macOS.
On macOS, we want to use `mkstemp` rather than `tmpnam`. Enable
POSIX support in Lua to fix that (and some other things). Since
POSIX support also enables the flag for POPEN, we no longer need
to do that directly for Linux.
2020-05-21 21:57:05 +07:00
rxi 6b39fb6dfb Changed autoreload to strip carriage-returns on reload 2020-05-21 09:58:47 +01:00
rxi 7aabfebfa0 Fixed mouse-position resolution when dropping a file 2020-05-20 10:33:08 +01:00
rxi 08ce7e2563 Fixed stuck mouse-drag-selection on dropped-file
Resolves #109
2020-05-20 09:52:01 +01:00
rxi bc4bf3d384 Added core.set_active_view(); removed `focusable` boolean from View 2020-05-19 14:55:46 +01:00
rxi 8ec717f240 Added temporary backwards compatibility for `core.project_dir` 2020-05-18 10:28:01 +01:00
rxi 4ae0d477c0 Made lite set project dir to CWD; removed core.project_dir
Fixes #100
2020-05-17 17:05:56 +01:00
rxi c1f731e5a1 Fixed EXEDIR having trailing slash 2020-05-17 16:58:32 +01:00
rxi adad2a65be Version 1.04 2020-05-17 14:58:44 +01:00
rxi adecaba292 Removed unused varibale in ./data/plugins/treeview.lua 2020-05-17 14:02:49 +01:00
rxi 4644154e5b Added `config.ignore_files`
Filenames that match either the single pattern provided by
`config.ignore_files`, or any pattern in a table of patterns, will be ignored
when lite is filling the `core.project_files` table

Resolves #77
Resolves #102
2020-05-17 13:38:45 +01:00
rxi 1abb979490 Added null-checks for all push_command() calls in rencache.c 2020-05-17 13:18:26 +01:00
rxi 82fdc63c6a Added global EXEFILE 2020-05-17 09:36:46 +01:00
rxi 7e7602c53c Removed `exec` plugin -- moved to `lite-plugins` repo 2020-05-16 10:06:35 +01:00
rxi 59f5692f2e Merge branch 'master' of https://github.com/rxi/lite 2020-05-16 10:00:13 +01:00
rxi 0dd4811465
Merge pull request #101 from demotulatingswan/master
Fixed various issues in exec:replace
2020-05-16 09:59:58 +01:00
rxi bc3147e1d0 Changed config.mouse_wheel_scroll default to multiply by SCALE 2020-05-16 09:46:31 +01:00
rxi 15129b49a6 Moved `config.treeview_size` from `config.lua` to `treeview.lua` 2020-05-16 09:44:31 +01:00
demotulatingswan 92b3b5ba86 Fixed various issues in exec:replace 2020-05-15 20:39:10 +02:00
rxi 5102088aca Deferred showing window until after the first frame has rendered
Fixes #97
2020-05-14 23:06:14 +01:00
rxi b8d2805502 Exposed doc's internal insert/remove: Doc:raw_insert|remove 2020-05-14 16:40:50 +01:00
rxi ef53453246 Fixed x-offset of text in `DocView:draw_line_gutter` 2020-05-14 13:26:21 +01:00
rxi 6525269386 Made tokenizer skip parsing process on plain-text files
This, along with the earlier rencache changes should resolve #64
2020-05-14 10:10:50 +01:00
rxi bcd1b3a081 Fixed gutter text jittering when horizontal scrolling on docview 2020-05-14 10:06:47 +01:00
rxi 9bf0ed2419 Made rencache warn on exhausting command buffer instead of panicing 2020-05-14 08:52:07 +01:00
rxi 4c2c03ed4d Made draw_text/draw_rect not push command if result is not on-screen 2020-05-14 08:45:45 +01:00
rxi 2b32edf7f0 Added system.exec() to system api 2020-05-13 20:32:53 +01:00
rxi e4ae088bb5 Added support for shift+click selecting
Resolves #71
Resolves #59
2020-05-13 16:12:20 +01:00
rxi 1f55fec94b Fixed triple-clicking on last line not selecting the line 2020-05-13 09:29:53 +01:00
rxi ed86f7d04e Fixed find-replace:select-next erroring on multiline selection 2020-05-12 20:05:17 +01:00
rxi 7f6a2710ef Made clicking the message on the StatusView open a LogView 2020-05-12 14:44:29 +01:00
rxi 23cf193026 Fixed lua's multi-line comment syntax pattern
Resolves #86
Resolves #87
2020-05-12 10:12:49 +01:00
rxi ff2c7bf5e5 Added double-backtick support to language_md plugin
Resolves #80
2020-05-11 20:31:35 +01:00
rxi af36658e68 Added custom suggestions support to autocomplete
`autocomplete.add()` can now be used to add additional auto complete suggestions
with descriptions
2020-05-11 19:37:50 +01:00
rxi 2f659d5180
Merge pull request #81 from extrowerk/patch-1
Build fix
2020-05-11 13:31:33 +01:00
extrowerk a6013ff181
Build fix 2020-05-11 13:49:18 +02:00
rxi c215eff6d8 Improved idle CPU utilisation when not-focused, added system.wait_event() 2020-05-11 00:21:07 +01:00
rxi b67b680975 Changed get_scale() to always default to 1.0 on macOS 2020-05-10 19:32:01 +01:00
rxi f7b54db1b4 Added `icon.inl linguist-vendored` to .gitattributes 2020-05-10 15:54:09 +01:00
rxi 3067443432
Merge pull request #70 from cptx032/master
Fixing trimwhitespace plugin bug
2020-05-10 15:41:13 +01:00
Willie Lawrence a17fe46c05 Fixing trimwhitespace plugin bug 2020-05-10 11:26:44 -03:00
rxi c4be26ccaa Added .gitattributes 2020-05-10 14:07:16 +01:00
rxi 22f563e712 Merge branch 'master' of https://github.com/rxi/lite 2020-05-10 13:56:31 +01:00
rxi ae20c40554
Merge pull request #61 from inad9300/patch-1
Update language_js.lua
2020-05-10 13:54:45 +01:00
Daniel M 2ed29cae11
Put back get and set 2020-05-10 10:56:28 +02:00
Daniel M 3974971ff9
Update language_js.lua 2020-05-10 10:51:45 +02:00
rxi 543234c42e Removed redundant line from syntax highlighter 2020-05-10 09:20:06 +01:00
rxi 8671b02bdc Whitespace 2020-05-10 09:14:12 +01:00
rxi 143f8867a1 Version 1.03 2020-05-09 16:29:34 +01:00
rxi 3d49b6d200 Fixed highlighter resetting of syntax on doc filename change 2020-05-09 16:28:18 +01:00
rxi 70f62f3c8a Added `doc:rename` command; changed command_view:set_text to take `select` argument 2020-05-09 16:09:07 +01:00
rxi 7479c1380d Added commands `root:shrink` and `root:grow` 2020-05-09 14:40:26 +01:00
rxi b08f870f47 Added stripping of carriage-returns when pasting 2020-05-09 14:22:13 +01:00
rxi 78e729f580 Updated README screenshot 2020-05-09 11:45:50 +01:00
rxi ee19d4644d Updated icon 2020-05-09 11:33:16 +01:00
rxi ffdaec47e8 Default color theme adjustments 2020-05-09 11:10:19 +01:00
rxi dfcbc48aad Replaced `build.py/build.config.py` with `build.sh` 2020-05-09 11:01:28 +01:00
rxi 18b7d70a91 Fixed rare case where core.doc.highlighter would iterate out of line bounds 2020-05-09 09:09:39 +01:00
rxi a651d48e84 Wrapped `core.on_event` calls in `core.try` 2020-05-09 08:38:51 +01:00
rxi 22171fa802 Simplified core.step() 2020-05-08 20:44:53 +01:00
rxi 31820b36ef Moved `syntax` from `doc.highlighter` to `doc` 2020-05-08 20:29:22 +01:00
rxi a754c60127 README improvements 2020-05-08 20:22:53 +01:00
rxi 2642f7443f Added default text to find-replace's "new text" input 2020-05-08 13:55:23 +01:00
rxi b2756d8a49 Removed xalloc.c/h 2020-05-08 13:41:39 +01:00
rxi 1d2a0aada5 Made `doc:toggle-line-comments` command skip empty lines 2020-05-08 09:07:13 +01:00
rxi bf8565d2a1 Fixed `doc:toggle-line-comments` for syntax highlighter relocation 2020-05-07 23:11:04 +01:00
rxi 95bdb07d49 Removed unused variables 2020-05-07 22:40:34 +01:00
rxi de94c8a13c Removed unused variables in core.init 2020-05-07 22:25:52 +01:00
rxi 774d95d800 Comment improvement in rencache.c 2020-05-07 22:24:12 +01:00
rxi 762c1e2b69 Fixed RootView's EmptyView keymap text
Broke due to command renaming
2020-05-07 21:46:21 +01:00
rxi f5025efbb8 Moved highlighter code from `DocView` to `Doc`
* Only one highlighter state is kept per-document as opposed
  to one per-docview
* Fixes a bug with retaining older highlighter state as a
  DocView wasn't able to detect lines changing above it's viewport
* Renames `highlighter` module to more descriptive `tokenizer`
2020-05-07 21:14:46 +01:00
rxi ae42176953 Renamed `common.matches_pattern` => `common.match_pattern` 2020-05-07 14:18:46 +01:00
rxi 7cdf7dc44f Changed TreeView to first initialize its size in :update() 2020-05-07 14:03:37 +01:00
rxi 5acc391288 Added logging when project module is loaded 2020-05-07 13:41:39 +01:00
rxi 596b40c741 Made `find-replace:find-symbol` use first symbol in sel as default 2020-05-07 10:55:11 +01:00
rxi 3057786ce2 Moved `matches_pattern` from `syntax` to `common` 2020-05-07 10:27:37 +01:00
rxi e551052e91 Added icon to non-windows build 2020-05-06 19:49:34 +01:00
rxi 201c8ffe9f Added stdout flushing to build.py 2020-05-06 19:34:18 +01:00
rxi 73996e3dc9 Changed core to store `modified` and `size` in project_files table 2020-05-06 17:03:10 +01:00
rxi 5361bfaf9c Added open-[project|user]-module commands, renamed command/file finder commands 2020-05-06 14:48:04 +01:00
rxi 271e5434d0 Added support for a `.lite_project.lua` file in project directory 2020-05-06 13:29:35 +01:00
rxi 7610e1064f Added multiline support for LogView's messages 2020-05-06 00:06:27 +01:00
rxi 15cfbfbc46 Increased config.max_log_items from 20 to 80 2020-05-05 23:40:42 +01:00
rxi 9bd6efddd7 Made separator and separator2 fields of StatusView 2020-05-03 22:44:49 +01:00
rxi d5ffee51ff Added StatusView:get_items() 2020-05-03 18:47:55 +01:00
rxi 844dced7cd Simplified StatusView item drawing 2020-05-03 18:43:24 +01:00
rxi d859ce5fcd Added mapping for `shift+delete` to keymap 2020-05-03 18:37:06 +01:00
rxi 69e6550eba Added mappings for `ctrl+delete` and `ctrl+shift+delete` to keymap 2020-05-03 18:35:16 +01:00
rxi 05ca43e637 Changed rounding type on common.draw_text() 2020-05-03 16:48:35 +01:00
rxi 468905830f Fixed indentation in build.config.py 2020-05-02 17:58:20 +01:00
rxi 26d6e558f1 Added brightness transition to projectsearch text/line on complete 2020-05-02 16:53:03 +01:00
rxi 0967740d88 Improved findreplace replace log and commandview text 2020-05-02 14:45:33 +01:00
rxi 5155ce0527 Added `replace-symbol` command to findreplace plugin 2020-05-02 11:14:07 +01:00
rxi 9fc185af2f Added scroll bounds
Resolves #9
Resolves #6
Resolves #3
2020-05-02 00:21:04 +01:00
rxi 28cdd3cabe Minor cleanup in core.statusview and core.commands.core 2020-05-01 20:17:10 +01:00
rxi a9f3079c90 Added `refresh` command (`f5`) to projectsearch plugin's ResultView 2020-05-01 19:32:02 +01:00
rxi 4ca35fe056 Added command `doc:select-none`, added binding to `escape` 2020-05-01 19:25:17 +01:00
rxi 885ed5f860 Improved handling of zero-sized locked nodes on RootView 2020-05-01 16:17:07 +01:00
rxi ab8510291e Added find-replace:select-next, bound to ctrl+d by default 2020-05-01 10:21:57 +01:00
rxi 044fdb3655 Prevented scroll-animation when LogView is initialized 2020-04-30 14:44:52 +01:00
rxi 4d39dcaded Changed `View:get_content_offset()` to round resultant values
Avoids some issues that occur with fractional offsets, most noticable
on rectangles drawn on DocView jittering by 1-pixel when scrolling
2020-04-30 14:43:25 +01:00
rxi 2090379892 Fixed multiple links on one line in markdown syntax 2020-04-27 18:45:06 +01:00
rxi 439537d63e Fixed format string passed to core.error() in core.try() 2020-04-26 21:40:25 +01:00
rxi 16c1039666 Changed README to link to `releases/latest` instead of `releases` 2020-04-26 14:46:43 +01:00
58 changed files with 3059 additions and 1110 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
winlib/* linguist-vendored
src/lib/* linguist-vendored
icon.inl linguist-vendored

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: rxi

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
src/*.o
src/*/*.o
src/*/*/*.o
lite
*.txt

105
Makefile Executable file
View File

@ -0,0 +1,105 @@
#
# Makefile generated by:
# codebench 0.55
#
# Project: lite
#
# Created on: 15-12-2021 22:58:23
#
#
###################################################################
##
##//// Objects
##
###################################################################
lite_OBJ := \
src/lib/stb/stb_truetype.o src/api/renderer.o src/api/renderer_font.o \
src/api/system.o src/main.o src/api/api.o \
src/rencache.o src/renderer.o
###################################################################
##
##//// Variables and Environment
##
###################################################################
CC := gcc:bin/gcc
INCPATH := -I. -Isrc
CFLAGS := $(INCPATH) -D__USE_INLINE__ -Wall -Werror -Wwrite-strings
###################################################################
##
##//// General rules
##
###################################################################
.PHONY: all all-before all-after clean clean-custom realclean
all: all-before lite all-after
all-before:
# You can add rules here to execute before the project is built
all-after:
# You can add rules here to execute after the project is built
clean: clean-custom
@echo "Cleaning compiler objects..."
@rm -f $(lite_OBJ)
realclean:
@echo "Cleaning compiler objects and targets..."
@rm -f $(lite_OBJ) lite
###################################################################
##
##//// Targets
##
###################################################################
lite: $(lite_OBJ)
@echo "Linking lite"
@gcc:bin/gcc -o lite $(lite_OBJ) -llua -lSDL2 -lpthread -lauto
@echo "Removing stale debug target: lite"
@rm -f lite.debug
###################################################################
##
##//// Standard rules
##
###################################################################
# A default rule to make all the objects listed below
# because we are hiding compiler commands from the output
.c.o:
@echo "Compiling $<"
@$(CC) -c $< -o $*.o $(CFLAGS)
src/api/api.o: src/api/api.c
src/api/renderer.o: src/api/renderer.c src/api/api.h src/renderer.h \
src/api/renderer_font.o: src/api/renderer_font.c src/api/api.h src/renderer.h \
src/api/system.o: src/api/system.c src/api/api.h
src/main.o: src/main.c src/api/api.h src/renderer.h \
src/rencache.o: src/rencache.c
src/renderer.o: src/renderer.c src/lib/stb/stb_truetype.h
src/lib/stb/stb_truetype.o: src/lib/stb/stb_truetype.c

View File

@ -1,26 +1,33 @@
# lite
![screenshot](https://user-images.githubusercontent.com/3920290/71542771-52265880-2962-11ea-8382-c92f8e10b734.png)
![screenshot](https://user-images.githubusercontent.com/3920290/81471642-6c165880-91ea-11ea-8cd1-fae7ae8f0bc4.png)
A lightweight text editor written in Lua
* **[Get lite](https://github.com/rxi/lite/releases/latest)** — Download
for Windows and Linux
* **[Get started](doc/usage.md)** — A quick overview on how to get started
* **[Get plugins](https://github.com/rxi/lite-plugins)** — Add additional
functionality
* **[Get color themes](https://github.com/rxi/lite-colors)** — Add additional colors
themes
## Overview
lite is a lightweight text editor written mostly in Lua — it aims to provide
something practical, pretty, *small* and responsive, implemented as simply as
something practical, pretty, *small* and fast, implemented as simply as
possible; easy to modify and extend, or to use without doing either.
## Get
Go to the **[releases](https://github.com/rxi/lite/releases)** page to download
precompiled binaries for Windows and Linux. Additional functionality can be
added through plugins which are available from the **[plugins
repository](https://github.com/rxi/lite-plugins)**; additional color themes can
be found in the **[colors repository](https://github.com/rxi/lite-colors)**. The
editor can be customized by making changes to the [user
module](data/user/init.lua).
## Customization
Additional functionality can be added through plugins which are available from
the [plugins repository](https://github.com/rxi/lite-plugins); additional color
themes can be found in the [colors repository](https://github.com/rxi/lite-colors).
The editor can be customized by making changes to the
[user module](data/user/init.lua).
## Building
You can build the project yourself on Linux using the provided `build.py`
script. Note that the project does not need to be rebuilt if you are only making
changes to the Lua portion of the code.
You can build the project yourself on Linux using the `build.sh` script
or on Windows using the `build.bat` script *([MinGW](https://nuwen.net/mingw.html) is required)*.
Note that the project does not need to be rebuilt if you are only making changes
to the Lua portion of the code.
## Contributing
Any additional functionality that can be added through a plugin should be done

16
build.bat Normal file
View File

@ -0,0 +1,16 @@
@echo off
rem download this:
rem https://nuwen.net/mingw.html
echo compiling (windows)...
windres res.rc -O coff -o res.res
gcc src/*.c src/api/*.c src/lib/lua52/*.c src/lib/stb/*.c^
-O3 -s -std=gnu11 -fno-strict-aliasing -Isrc -DLUA_USE_POPEN^
-Iwinlib/SDL2-2.0.10/x86_64-w64-mingw32/include^
-lmingw32 -lm -lSDL2main -lSDL2 -Lwinlib/SDL2-2.0.10/x86_64-w64-mingw32/lib^
-mwindows res.res^
-o lite.exe
echo done

View File

@ -1,29 +0,0 @@
import os
cflags = [ "-Wall", "-O3", "-g", "-DLUA_USE_POPEN" ]
lflags = [ "-lSDL2", "-lm" ]
include = [ "src" ]
output = "lite"
if "sanitize" in opt:
log("address sanitizer enabled")
cflags += [ "-fsanitize=address" ]
lflags += [ "-fsanitize=address" ]
if "windows" in opt:
compiler = "x86_64-w64-mingw32-gcc"
output += ".exe"
cflags += [ "-Iwinlib/SDL2-2.0.10/x86_64-w64-mingw32/include" ]
lflags += [ "-Lwinlib/SDL2-2.0.10/x86_64-w64-mingw32/lib" ]
lflags = [ "-lmingw32", "-lSDL2main" ] + lflags
lflags += [ "-lwinmm" ]
lflags += [ "-mwindows" ]
lflags += [ "res.res" ]
def pre():
os.system("x86_64-w64-mingw32-windres res.rc -O coff -o res.res")
def post():
os.remove("res.res")

306
build.py
View File

@ -1,306 +0,0 @@
#!/usr/bin/python2.7
import os, sys, platform, shutil
import re, threading, time, json
from os import path
from hashlib import sha1
from multiprocessing import cpu_count
config_file = "build.config.py"
cache_dir = ".buildcache"
object_dir = path.join(cache_dir, "obj")
cache_file = path.join(cache_dir, "cache.json")
max_workers = cpu_count()
config = {
"compiler" : "gcc",
"output" : "a.out",
"source" : [ "src" ],
"include" : [],
"cflags" : [],
"lflags" : [],
"run" : "./{output}"
}
Hint, Warn, Error = range(3)
log_prefix = {
Hint: "\x1b[32mHint:\x1b[0m",
Warn: "\x1b[33mWarn:\x1b[0m",
Error: "\x1b[31;1mError:\x1b[0m"
}
log_lock = threading.Lock()
def log(msg, mode=Hint):
log_lock.acquire()
print log_prefix[mode], msg
log_lock.release()
def error(msg):
log(msg, mode=Error)
os._exit(1)
def load_config(filename):
""" loads the given config file into the `config` global dict """
if not path.exists(filename):
error("config file does not exist: '%s'" % filename)
d = {
"opt": sys.argv,
"platform": platform.system(),
"error": error,
"log": log,
"Hint": Hint,
"Warn": Warn,
"Error": Error
}
execfile(filename, d)
config.update(d)
if len(config["source"]) == 0:
error("no source directories specified in config")
def load_cache(cache_file):
if not path.exists(cache_file):
return { "hashes": [], "cmd": "" }
with open(cache_file) as fp:
log("loaded cache")
return json.load(fp)
def update_cache(cache_file, obj):
with open(cache_file, "wb") as fp:
json.dump(obj, fp, indent=2)
log("updated cache")
def resolve_file(filename, dir):
""" finds the actual location of an included file """
f = path.join(dir, filename)
if path.exists(f):
return short_name(f)
for dir in config["include"]:
f = path.join(dir, filename)
if path.exists(f):
return short_name(f)
file_info_cache = {}
def get_file_info(filename):
""" returns a dict of file info for the given file """
if filename in file_info_cache:
return file_info_cache[filename]
hash = sha1()
includes = []
with open(filename) as fp:
for line in fp.readlines():
# get includes
if "#include" in line:
match = re.match('^\s*#include\s+"(.*?)"', line)
if match:
includes.append( match.group(1) )
# update hash
hash.update(line)
hash.update("\n")
res = { "hash": hash.hexdigest(), "includes": includes }
file_info_cache[filename] = res
return res
def short_name(filename):
""" returns the filename relative to the current path """
n = len(path.abspath("."))
return path.abspath(filename)[n+1:]
def get_deep_hash(filename):
""" creates a hash from the file and all its includes """
h = sha1()
processed = set()
files = [ resolve_file(filename, ".") ]
while len(files) > 0:
f = files.pop()
info = get_file_info(f)
processed.add(f)
# update hash
h.update(info["hash"])
# add includes
for x in info["includes"]:
resolved = resolve_file(x, path.dirname(f))
if resolved:
if resolved not in processed:
files.append(resolved)
else:
log("could not resolve file '%s'" % x, mode=Warn)
return h.hexdigest()
def build_deep_hash_dict(cfiles):
""" returns a dict mapping each cfile to its hash """
res = {}
for f in cfiles:
res[f] = get_deep_hash(f)
return res
def get_cfiles():
""" returns all .h and .c files in source directories """
res = []
for dir in config["source"]:
for root, dirs, files in os.walk(dir):
for file in files:
if file.endswith((".c", ".h")):
f = path.join(root, file)
res.append( short_name(f) )
return res
def build_compile_cmd():
""" creates the command used to compile files """
lst = [
config["compiler"],
" ".join(map(lambda x: "-I" + x, config["include"])),
" ".join(config["cflags"]),
"-c", "{infile}", "-o", "{outfile}"
]
return " ".join(lst)
def obj_name(filename):
""" creates the object file name for a given filename """
filename = re.sub("[^\w]+", "_", filename)
return filename[:-2] + "_" + sha1(filename).hexdigest()[:8] + ".o"
def compile(cmd, filename):
""" compiles the given file into an object file using the cmd """
log("compiling '%s'" % filename)
outfile = path.join(object_dir, obj_name(filename))
res = os.system(cmd.format(infile=filename, outfile=outfile))
if res != 0:
error("failed to compile '%s'" % filename)
def link():
""" links objects and outputs the final binary """
log("linking")
lst = [
config["compiler"],
"-o", config["output"],
path.join(object_dir, "*"),
" ".join(config["lflags"])
]
cmd = " ".join(lst)
res = os.system(cmd)
if res != 0:
error("failed to link")
def parallel(func, workers=4):
""" runs func on multiple threads and waits for them all to finish """
threads = []
for i in range(workers):
t = threading.Thread(target=func)
threads.append(t)
t.start()
for t in threads:
t.join()
if __name__ == "__main__":
start_time = time.time()
load_config(config_file)
run_at_exit = False
output_dir = path.join(".", path.dirname(config["output"]))
cache = load_cache(cache_file)
cmd = build_compile_cmd()
if "run" in sys.argv:
run_at_exit = True
if cache["cmd"] != cmd:
sys.argv.append("clean")
if "clean" in sys.argv:
log("performing clean build")
shutil.rmtree(cache_dir, ignore_errors=True)
cache = load_cache(cache_file)
if not path.exists(object_dir):
os.makedirs(object_dir)
if not path.exists(output_dir):
os.makedirs(output_dir)
if "pre" in config:
config["pre"]()
cfiles = get_cfiles()
hashes = build_deep_hash_dict(cfiles)
# delete object files for cfiles that no longer exist
obj_files = set(map(obj_name, cfiles))
for f in os.listdir(object_dir):
if f not in obj_files:
os.remove(path.join(object_dir, f))
# build list of all .c files that need compiling
pending = []
for f in cfiles:
if f.endswith(".c"):
if f not in cache["hashes"] or cache["hashes"][f] != hashes[f]:
pending.append(f)
# compile files until there are none left
def worker():
while True:
try:
f = pending.pop()
except:
break
compile(cmd, f)
parallel(worker, workers=max_workers)
link()
update_cache(cache_file, { "hashes": hashes, "cmd": cmd })
if "post" in config:
config["post"]()
log("done [%.2fs]" % (time.time() - start_time))
if run_at_exit:
log("running")
cmd = config["run"].format(output=config["output"])
os.system(cmd)

43
build.sh Executable file
View File

@ -0,0 +1,43 @@
#!/bin/bash
cflags="-Wall -O3 -g -std=gnu11 -fno-strict-aliasing -Isrc"
lflags="-lSDL2 -lm"
if [[ $* == *windows* ]]; then
platform="windows"
outfile="lite.exe"
compiler="x86_64-w64-mingw32-gcc"
cflags="$cflags -DLUA_USE_POPEN -Iwinlib/SDL2-2.0.10/x86_64-w64-mingw32/include"
lflags="$lflags -Lwinlib/SDL2-2.0.10/x86_64-w64-mingw32/lib"
lflags="-lmingw32 -lSDL2main $lflags -mwindows -o $outfile res.res"
x86_64-w64-mingw32-windres res.rc -O coff -o res.res
else
platform="unix"
outfile="lite"
compiler="gcc"
cflags="$cflags -DLUA_USE_POSIX"
lflags="$lflags -o $outfile"
fi
if command -v ccache >/dev/null; then
compiler="ccache $compiler"
fi
echo "compiling ($platform)..."
for f in `find src -name "*.c"`; do
$compiler -c $cflags $f -o "${f//\//_}.o"
if [[ $? -ne 0 ]]; then
got_error=true
fi
done
if [[ ! $got_error ]]; then
echo "linking..."
$compiler *.o $lflags
fi
echo "cleaning up..."
rm *.o
rm res.res 2>/dev/null
echo "done"

View File

@ -1,6 +1,6 @@
#!/bin/bash
./build.py release windows
./build.py release
./build.sh release windows
./build.sh release
rm lite.zip 2>/dev/null
cp winlib/SDL2-2.0.10/x86_64-w64-mingw32/bin/SDL2.dll SDL2.dll
strip lite

View File

@ -2,8 +2,6 @@ local core = require "core"
local common = require "core.common"
local command = require "core.command"
local keymap = require "core.keymap"
local Doc = require "core.doc"
local DocView = require "core.docview"
local LogView = require "core.logview"
@ -37,7 +35,7 @@ command.add(nil, {
end)
end,
["core:do-command"] = function()
["core:find-command"] = function()
local commands = command.get_all_valid()
core.command_view:enter("Do Command", function(text, item)
if item then
@ -56,25 +54,25 @@ command.add(nil, {
end)
end,
["core:new-doc"] = function()
core.root_view:open_doc(core.open_doc())
end,
["core:open-project-file"] = function()
core.command_view:enter("Open Project File", function(text, item)
text = core.project_dir .. PATHSEP .. (item and item.text or text)
["core:find-file"] = function()
core.command_view:enter("Open File From Project", function(text, item)
text = item and item.text or text
core.root_view:open_doc(core.open_doc(text))
end, function(text)
local files = {}
for _, item in pairs(core.project_files) do
if item.type == "file" then
table.insert(files, item.filename:sub(#core.project_dir + 2))
table.insert(files, item.filename)
end
end
return common.fuzzy_match(files, text)
end)
end,
["core:new-doc"] = function()
core.root_view:open_doc(core.open_doc())
end,
["core:open-file"] = function()
core.command_view:enter("Open File", function(text)
core.root_view:open_doc(core.open_doc(text))
@ -85,4 +83,19 @@ command.add(nil, {
local node = core.root_view:get_active_node()
node:add_view(LogView())
end,
["core:open-user-module"] = function()
core.root_view:open_doc(core.open_doc(EXEDIR .. "/data/user/init.lua"))
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)
end
end,
})

View File

@ -3,7 +3,6 @@ local command = require "core.command"
local common = require "core.common"
local config = require "core.config"
local translate = require "core.doc.translate"
local search = require "core.doc.search"
local DocView = require "core.docview"
@ -25,25 +24,29 @@ local function get_indent_string()
end
local function insert_at_start_of_selected_lines(text)
local function insert_at_start_of_selected_lines(text, skip_empty)
local line1, col1, line2, col2, swap = doc():get_selection(true)
for line = line1, line2 do
doc():insert(line, 1, text)
local line_text = doc().lines[line]
if (not skip_empty or line_text:find("%S")) then
doc():insert(line, 1, text)
end
end
doc():set_selection(line1, col1 + #text, line2, col2 + #text, swap)
end
local function remove_from_start_of_selected_lines(text)
local function remove_from_start_of_selected_lines(text, skip_empty)
local line1, col1, line2, col2, swap = doc():get_selection(true)
for line = line1, line2 do
if doc().lines[line]:sub(1, #text) == text then
local line_text = doc().lines[line]
if line_text:sub(1, #text) == text
and (not skip_empty or line_text:find("%S"))
then
doc():remove(line, 1, line, #text + 1)
if line == line1 then col1 = col1 - #text end
if line == line2 then col2 = col2 - #text end
end
end
doc():set_selection(line1, col1, line2, col2, swap)
doc():set_selection(line1, col1 - #text, line2, col2 - #text, swap)
end
@ -85,7 +88,7 @@ local commands = {
end,
["doc:paste"] = function()
doc():text_input(system.get_clipboard())
doc():text_input(system.get_clipboard():gsub("\r", ""))
end,
["doc:newline"] = function()
@ -135,6 +138,11 @@ local commands = {
doc():set_selection(1, 1, math.huge, math.huge)
end,
["doc:select-none"] = function()
local line, col = doc():get_selection()
doc():set_selection(line, col)
end,
["doc:select-lines"] = function()
local line1, _, line2, _, swap = doc():get_selection(true)
append_line_if_last_line(line2)
@ -152,7 +160,9 @@ local commands = {
local line1, _, line2 = doc():get_selection(true)
if line1 == line2 then line2 = line2 + 1 end
local text = doc():get_text(line1, 1, line2, math.huge)
text = text:gsub("\n[\t ]*", " ")
text = text:gsub("(.-)\n[\t ]*", function(x)
return x:find("^%s*$") and x or x .. " "
end)
doc():insert(line1, 1, text)
doc():remove(line1, #text + 1, line2, math.huge)
if doc():has_selection() then
@ -213,21 +223,21 @@ local commands = {
end,
["doc:toggle-line-comments"] = function()
if not dv().syntax.comment then return end
local text = dv().syntax.comment .. " "
local comment = doc().syntax.comment
if not comment then return end
local comment_text = comment .. " "
local line1, _, line2 = doc():get_selection(true)
local uncomment = true
for line = line1, line2 do
local str = doc().lines[line]:match("^[ \t]*(.*)$")
if str and str:sub(1, #text) ~= text then
local text = doc().lines[line]
if text:find("%S") and text:find(comment_text, 1, true) ~= 1 then
uncomment = false
break
end
end
if uncomment then
remove_from_start_of_selected_lines(text)
remove_from_start_of_selected_lines(comment_text, true)
else
insert_at_start_of_selected_lines(text)
insert_at_start_of_selected_lines(comment_text, true)
end
end,
@ -270,6 +280,10 @@ local commands = {
end)
end,
["doc:toggle-line-ending"] = function()
doc().crlf = not doc().crlf
end,
["doc:save-as"] = function()
if doc().filename then
core.command_view:set_text(doc().filename)
@ -287,8 +301,20 @@ local commands = {
end
end,
["doc:toggle-line-ending"] = function()
doc().crlf = not doc().crlf
["doc:rename"] = function()
local old_filename = doc().filename
if not old_filename then
core.error("Cannot rename unsaved doc")
return
end
core.command_view:set_text(old_filename)
core.command_view:enter("Rename", function(filename)
doc():save(filename)
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
if filename ~= old_filename then
os.remove(old_filename)
end
end, common.path_suggest)
end,
}
@ -296,10 +322,10 @@ local commands = {
local translations = {
["previous-char"] = translate.previous_char,
["next-char"] = translate.next_char,
["previous-word-boundary"] = translate.previous_word_boundary,
["next-word-boundary"] = translate.next_word_boundary,
["previous-start-of-block"] = translate.previous_start_of_block,
["next-start-of-block"] = translate.next_start_of_block,
["previous-word-start"] = translate.previous_word_start,
["next-word-end"] = translate.next_word_end,
["previous-block-start"] = translate.previous_block_start,
["next-block-end"] = translate.next_block_end,
["start-of-doc"] = translate.start_of_doc,
["end-of-doc"] = translate.end_of_doc,
["start-of-line"] = translate.start_of_line,

View File

@ -1,5 +1,4 @@
local core = require "core"
local common = require "core.common"
local command = require "core.command"
local config = require "core.config"
local search = require "core.doc.search"
@ -36,8 +35,7 @@ local function find(label, search_fn)
local text = dv.doc:get_text(table.unpack(sel))
local found = false
core.command_view:set_text(text)
core.command_view.doc:set_selection(math.huge, math.huge, 1, 1)
core.command_view:set_text(text, true)
core.command_view:enter(label, function(text)
if found then
@ -47,44 +45,60 @@ local function find(label, search_fn)
else
core.error("Couldn't find %q", text)
dv.doc:set_selection(table.unpack(sel))
dv:scroll_to_make_visible(sel[1], sel[2])
end
end, function(text)
local ok, line1, col1, line2, col2 = pcall(search_fn, dv.doc, sel[1], sel[2], text)
if text == "" then
dv.doc:set_selection(table.unpack(sel))
elseif ok and line1 then
if ok and line1 and text ~= "" then
dv.doc:set_selection(line2, col2, line1, col1)
dv:scroll_to_line(line2, true)
found = true
else
dv.doc:set_selection(table.unpack(sel))
found = false
end
end, function(explicit)
if explicit then
dv.doc:set_selection(table.unpack(sel))
dv:scroll_to_make_visible(sel[1], sel[2])
end
end)
end
local function replace(pattern_escape)
core.command_view:enter("Find To Replace", function(old)
core.command_view:enter("Replace \"" .. old .. "\" With", function(new)
local function replace(kind, default, fn)
core.command_view:set_text(default, true)
core.command_view:enter("Find To Replace " .. kind, function(old)
core.command_view:set_text(old, true)
local s = string.format("Replace %s %q With", kind, old)
core.command_view:enter(s, function(new)
local n = doc():replace(function(text)
if pattern_escape then
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
else
return text:gsub(old, new)
end
return fn(text, old, new)
end)
core.log("Replaced %d instance(s) of %q with %q", n, old, new)
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
end)
end)
end
local function has_selection()
return core.active_view:is(DocView)
and core.active_view.doc:has_selection()
end
command.add(has_selection, {
["find-replace:select-next"] = function()
local l1, c1, l2, c2 = doc():get_selection(true)
local text = doc():get_text(l1, c1, l2, c2)
local l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
if l2 then doc():set_selection(l2, c2, l1, c1) end
end
})
command.add("core.docview", {
["find-replace:find"] = function()
find("Find Text", function(doc, line, col, text)
@ -125,10 +139,32 @@ command.add("core.docview", {
end,
["find-replace:replace"] = function()
replace(true)
replace("Text", "", function(text, old, new)
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
end)
end,
["find-replace:replace-pattern"] = function()
replace(false)
replace("Pattern", "", function(text, old, new)
return text:gsub(old, new)
end)
end,
["find-replace:replace-symbol"] = function()
local first = ""
if doc():has_selection() then
local text = doc():get_text(doc():get_selection())
first = text:match(config.symbol_pattern) or ""
end
replace("Symbol", first, function(text, old, new)
local n = 0
local res = text:gsub(config.symbol_pattern, function(sym)
if old == sym then
n = n + 1
return new
end
end)
return res, n
end)
end,
})

View File

@ -2,6 +2,7 @@ local core = require "core"
local style = require "core.style"
local DocView = require "core.docview"
local command = require "core.command"
local common = require "core.common"
local t = {
@ -43,6 +44,20 @@ local t = {
table.insert(node.views, idx + 1, core.active_view)
end
end,
["root:shrink"] = function()
local node = core.root_view:get_active_node()
local parent = node:get_parent_node(core.root_view.root_node)
local n = (parent.a == node) and -0.1 or 0.1
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
end,
["root:grow"] = function()
local node = core.root_view:get_active_node()
local parent = node:get_parent_node(core.root_view.root_node)
local n = (parent.a == node) and 0.1 or -0.1
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
end,
}
@ -79,7 +94,7 @@ for _, dir in ipairs { "left", "right", "up", "down" } do
end
local node = core.root_view.root_node:get_child_overlapping_point(x, y)
if not node:get_locked_size() then
core.active_view = node.active_view
core.set_active_view(node.active_view)
end
end
end

View File

@ -1,5 +1,4 @@
local core = require "core"
local config = require "core.config"
local common = require "core.common"
local style = require "core.style"
local Doc = require "core.doc"
@ -71,9 +70,12 @@ function CommandView:get_text()
end
function CommandView:set_text(text)
function CommandView:set_text(text, select)
self.doc:remove(1, 1, math.huge, math.huge)
self.doc:text_input(text)
if select then
self.doc:set_selection(math.huge, math.huge, 1, 1)
end
end
@ -109,9 +111,8 @@ function CommandView:enter(text, submit, suggest, cancel)
submit = submit or noop,
suggest = suggest or noop,
cancel = cancel or noop,
view = core.active_view
}
core.active_view = self
core.set_active_view(self)
self:update_suggestions()
self.gutter_text_brightness = 100
self.label = text .. ": "
@ -120,7 +121,7 @@ end
function CommandView:exit(submitted, inexplicit)
if core.active_view == self then
core.active_view = self.state.view
core.set_active_view(core.last_active_view)
end
local cancel = self.state.cancel
self.state = default_state
@ -209,6 +210,7 @@ function CommandView:draw_line_gutter(idx, x, y)
local pos = self.position
local color = common.lerp(style.text, style.accent, self.gutter_text_brightness / 100)
core.push_clip_rect(pos.x, pos.y, self:get_gutter_width(), self.size.y)
x = x + style.padding.x
renderer.draw_text(self:get_font(), self.label, x, y + yoffset, color)
core.pop_clip_rect()
end
@ -217,7 +219,6 @@ end
local function draw_suggestions_box(self)
local lh = self:get_suggestion_line_height()
local dh = style.divider_size
local offsety = self:get_line_text_y_offset()
local x, _ = self:get_line_screen_position()
local h = math.ceil(self.suggestions_height)
local rx, ry, rw, rh = self.position.x, self.position.y - h - dh, self.size.x, h

View File

@ -60,7 +60,7 @@ end
local function fuzzy_match_items(items, needle)
local res = {}
for i, item in ipairs(items) do
for _, item in ipairs(items) do
local score = system.fuzzy_match(tostring(item), needle)
if score then
table.insert(res, { text = item, score = score })
@ -102,6 +102,18 @@ function common.path_suggest(text)
end
function common.match_pattern(text, pattern, ...)
if type(pattern) == "string" then
return text:find(pattern, ...)
end
for _, p in ipairs(pattern) do
local s, e = common.match_pattern(text, p, ...)
if s then return s, e end
end
return false
end
function common.draw_text(font, color, text, align, x,y,w,h)
local tw, th = font:get_width(text), font:get_height(text)
if align == "center" then
@ -109,7 +121,7 @@ function common.draw_text(font, color, text, align, x,y,w,h)
elseif align == "right" then
x = x + (w - tw)
end
y = math.ceil(y + (h - th) / 2)
y = common.round(y + (h - th) / 2)
return renderer.draw_text(font, text, x, y, color), y + th
end

View File

@ -2,13 +2,13 @@ local config = {}
config.project_scan_rate = 5
config.fps = 60
config.max_log_items = 20
config.max_log_items = 80
config.message_timeout = 3
config.mouse_wheel_scroll = 50
config.mouse_wheel_scroll = 50 * SCALE
config.file_size_limit = 10
config.ignore_files = "^%."
config.symbol_pattern = "[%a_][%w_]*"
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
config.treeview_size = 200 * SCALE
config.undo_merge_timeout = 0.3
config.max_undos = 10000
config.highlight_current_line = true

View File

@ -0,0 +1,80 @@
local core = require "core"
local config = require "core.config"
local tokenizer = require "core.tokenizer"
local Object = require "core.object"
local Highlighter = Object:extend()
function Highlighter:new(doc)
self.doc = doc
self:reset()
-- init incremental syntax highlighting
core.add_thread(function()
while true do
if self.first_invalid_line > self.max_wanted_line then
self.max_wanted_line = 0
coroutine.yield(1 / config.fps)
else
local max = math.min(self.first_invalid_line + 40, self.max_wanted_line)
for i = self.first_invalid_line, max do
local state = (i > 1) and self.lines[i - 1].state
local line = self.lines[i]
if not (line and line.init_state == state) then
self.lines[i] = self:tokenize_line(i, state)
end
end
self.first_invalid_line = max + 1
core.redraw = true
coroutine.yield()
end
end
end, self)
end
function Highlighter:reset()
self.lines = {}
self.first_invalid_line = 1
self.max_wanted_line = 0
end
function Highlighter:invalidate(idx)
self.first_invalid_line = math.min(self.first_invalid_line, idx)
self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines)
end
function Highlighter:tokenize_line(idx, state)
local res = {}
res.init_state = state
res.text = self.doc.lines[idx]
res.tokens, res.state = tokenizer.tokenize(self.doc.syntax, res.text, state)
return res
end
function Highlighter:get_line(idx)
local line = self.lines[idx]
if not line or line.text ~= self.doc.lines[idx] then
local prev = self.lines[idx - 1]
line = self:tokenize_line(idx, prev and prev.state)
self.lines[idx] = line
end
self.max_wanted_line = math.max(self.max_wanted_line, idx)
return line
end
function Highlighter:each_token(idx)
return tokenizer.each_token(self:get_line(idx).tokens)
end
return Highlighter

View File

@ -1,4 +1,6 @@
local Object = require "core.object"
local Highlighter = require "core.doc.highlighter"
local syntax = require "core.syntax"
local config = require "core.config"
local common = require "core.common"
@ -19,7 +21,6 @@ local function splice(t, at, remove, insert)
insert = insert or {}
local offset = #insert - remove
local old_len = #t
local new_len = old_len + offset
if offset < 0 then
for i = at - offset, old_len - offset do
t[i + offset] = t[i]
@ -49,6 +50,18 @@ function Doc:reset()
self.undo_stack = { idx = 1 }
self.redo_stack = { idx = 1 }
self.clean_change_id = 1
self.highlighter = Highlighter(self)
self:reset_syntax()
end
function Doc:reset_syntax()
local header = self:get_text(1, 1, self:position_offset(1, 1, 128))
local syn = syntax.get(self.filename or "", header)
if self.syntax ~= syn then
self.syntax = syn
self.highlighter:reset()
end
end
@ -68,6 +81,7 @@ function Doc:load(filename)
table.insert(self.lines, "\n")
end
fp:close()
self:reset_syntax()
end
@ -80,6 +94,7 @@ function Doc:save(filename)
end
fp:close()
self.filename = filename or self.filename
self:reset_syntax()
self:clean()
end
@ -211,11 +226,43 @@ function Doc:get_char(line, col)
end
local push_undo
local function push_undo(undo_stack, time, type, ...)
undo_stack[undo_stack.idx] = { type = type, time = time, ... }
undo_stack[undo_stack.idx - config.max_undos] = nil
undo_stack.idx = undo_stack.idx + 1
end
local function insert(self, undo_stack, time, line, col, text)
line, col = self:sanitize_position(line, col)
local function pop_undo(self, undo_stack, redo_stack)
-- pop command
local cmd = undo_stack[undo_stack.idx - 1]
if not cmd then return end
undo_stack.idx = undo_stack.idx - 1
-- handle command
if cmd.type == "insert" then
local line, col, text = table.unpack(cmd)
self:raw_insert(line, col, text, redo_stack, cmd.time)
elseif cmd.type == "remove" then
local line1, col1, line2, col2 = table.unpack(cmd)
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
elseif cmd.type == "selection" then
self.selection.a.line, self.selection.a.col = cmd[1], cmd[2]
self.selection.b.line, self.selection.b.col = cmd[3], cmd[4]
end
-- if next undo command is within the merge timeout then treat as a single
-- command and continue to execute it
local next = undo_stack[undo_stack.idx - 1]
if next and math.abs(cmd.time - next.time) < config.undo_merge_timeout then
return pop_undo(self, undo_stack, redo_stack)
end
end
function Doc:raw_insert(line, col, text, undo_stack, time)
-- split text into lines and merge with line at insertion point
local lines = split_lines(text)
local before = self.lines[line]:sub(1, col - 1)
@ -231,20 +278,20 @@ local function insert(self, undo_stack, time, line, col, text)
-- push undo
local line2, col2 = self:position_offset(line, col, #text)
push_undo(self, undo_stack, time, "selection", self:get_selection())
push_undo(self, undo_stack, time, "remove", line, col, line2, col2)
push_undo(undo_stack, time, "selection", self:get_selection())
push_undo(undo_stack, time, "remove", line, col, line2, col2)
-- update highlighter and assure selection is in bounds
self.highlighter:invalidate(line)
self:sanitize_selection()
end
local function remove(self, undo_stack, time, line1, col1, line2, col2)
line1, col1 = self:sanitize_position(line1, col1)
line2, col2 = self:sanitize_position(line2, col2)
line1, col1, line2, col2 = sort_positions(line1, col1, line2, col2)
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
-- push undo
local text = self:get_text(line1, col1, line2, col2)
push_undo(self, undo_stack, time, "selection", self:get_selection())
push_undo(self, undo_stack, time, "insert", line1, col1, text)
push_undo(undo_stack, time, "selection", self:get_selection())
push_undo(undo_stack, time, "insert", line1, col1, text)
-- get line content before/after removed text
local before = self.lines[line1]:sub(1, col1 - 1)
@ -252,54 +299,26 @@ local function remove(self, undo_stack, time, line1, col1, line2, col2)
-- splice line into line array
splice(self.lines, line1, line2 - line1 + 1, { before .. after })
end
function Doc:insert(...)
insert(self, self.undo_stack, system.get_time(), ...)
-- update highlighter and assure selection is in bounds
self.highlighter:invalidate(line1)
self:sanitize_selection()
end
function Doc:insert(line, col, text)
self.redo_stack = { idx = 1 }
line, col = self:sanitize_position(line, col)
self:raw_insert(line, col, text, self.undo_stack, system.get_time())
end
function Doc:remove(...)
remove(self, self.undo_stack, system.get_time(), ...)
self:sanitize_selection()
function Doc:remove(line1, col1, line2, col2)
self.redo_stack = { idx = 1 }
end
function push_undo(self, undo_stack, time, type, ...)
undo_stack[undo_stack.idx] = { type = type, time = time, ... }
undo_stack[undo_stack.idx - config.max_undos] = nil
undo_stack.idx = undo_stack.idx + 1
end
local function pop_undo(self, undo_stack, redo_stack)
-- pop command
local cmd = undo_stack[undo_stack.idx - 1]
if not cmd then return end
undo_stack.idx = undo_stack.idx - 1
-- handle command
if cmd.type == "insert" then
insert(self, redo_stack, cmd.time, table.unpack(cmd))
elseif cmd.type == "remove" then
remove(self, redo_stack, cmd.time, table.unpack(cmd))
elseif cmd.type == "selection" then
self.selection.a.line, self.selection.a.col = cmd[1], cmd[2]
self.selection.b.line, self.selection.b.col = cmd[3], cmd[4]
end
-- if next undo command is within the merge timeout then treat as a single
-- command and continue to execute it
local next = undo_stack[undo_stack.idx - 1]
if next and math.abs(cmd.time - next.time) < config.undo_merge_timeout then
return pop_undo(self, undo_stack, redo_stack)
end
line1, col1 = self:sanitize_position(line1, col1)
line2, col2 = self:sanitize_position(line2, col2)
line1, col1, line2, col2 = sort_positions(line1, col1, line2, col2)
self:raw_remove(line1, col1, line2, col2, self.undo_stack, system.get_time())
end

View File

@ -28,33 +28,32 @@ function translate.next_char(doc, line, col)
end
function translate.previous_word_boundary(doc, line, col)
local char = doc:get_char(doc:position_offset(line, col, -1))
local inword = not is_non_word(char)
repeat
local line2, col2 = line, col
line, col = doc:position_offset(line, col, -1)
if line == line2 and col == col2 then
function translate.previous_word_start(doc, line, col)
local prev
while line > 1 or col > 1 do
local l, c = doc:position_offset(line, col, -1)
local char = doc:get_char(l, c)
if prev and prev ~= char or not is_non_word(char) then
break
end
local c = doc:get_char(doc:position_offset(line, col, -1))
until inword and is_non_word(c) or not inword and c ~= char
return line, col
prev, line, col = char, l, c
end
return translate.start_of_word(doc, line, col)
end
function translate.next_word_boundary(doc, line, col)
local char = doc:get_char(line, col)
local inword = not is_non_word(char)
repeat
local line2, col2 = line, col
line, col = doc:position_offset(line, col, 1)
if line == line2 and col == col2 then
function translate.next_word_end(doc, line, col)
local prev
local end_line, end_col = translate.end_of_doc(doc, line, col)
while line < end_line or col < end_col do
local char = doc:get_char(line, col)
if prev and prev ~= char or not is_non_word(char) then
break
end
local c = doc:get_char(line, col)
until inword and is_non_word(c) or not inword and c ~= char
return line, col
line, col = doc:position_offset(line, col, 1)
prev = char
end
return translate.end_of_word(doc, line, col)
end
@ -86,30 +85,30 @@ function translate.end_of_word(doc, line, col)
end
function translate.previous_start_of_block(doc, line, col)
function translate.previous_block_start(doc, line, col)
while true do
line = line - 1
if line <= 1 then
return 1, 1
end
if doc.lines[line-1]:match("^%s*$")
and not doc.lines[line]:match("^%s*$") then
if doc.lines[line-1]:find("^%s*$")
and not doc.lines[line]:find("^%s*$") then
return line, (doc.lines[line]:find("%S"))
end
end
end
function translate.next_start_of_block(doc, line, col)
function translate.next_block_end(doc, line, col)
while true do
line = line + 1
if line >= #doc.lines then
return #doc.lines, 1
end
if doc.lines[line-1]:match("^%s*$")
and not doc.lines[line]:match("^%s*$") then
return line, (doc.lines[line]:find("%S"))
if doc.lines[line+1]:find("^%s*$")
and not doc.lines[line]:find("^%s*$") then
return line+1, #doc.lines[line+1]
end
line = line + 1
end
end

View File

@ -2,10 +2,9 @@ local core = require "core"
local common = require "core.common"
local config = require "core.config"
local style = require "core.style"
local syntax = require "core.syntax"
local keymap = require "core.keymap"
local translate = require "core.doc.translate"
local View = require "core.view"
local highlighter = require "core.highlighter"
local DocView = View:extend()
@ -51,15 +50,6 @@ DocView.translate = {
local blink_period = 0.8
local function reset_syntax(self)
local syn = syntax.get(self.doc.filename or "")
if self.syntax ~= syn then
self.syntax = syn
self.cache = { last_valid = 1 }
end
end
function DocView:new(doc)
DocView.super.new(self)
self.cursor = "ibeam"
@ -68,32 +58,6 @@ function DocView:new(doc)
self.font = "code_font"
self.last_x_offset = {}
self.blink_timer = 0
reset_syntax(self)
-- init thread for incremental highlighting
self.updated_highlighting = false
core.add_thread(function()
while true do
local _, max = self:get_visible_line_range()
if self.cache.last_valid > max then
coroutine.yield(1 / config.fps)
else
max = math.min(self.cache.last_valid + 20, max)
for i = self.cache.last_valid, max do
local state = (i > 1) and self.cache[i - 1].state
local cl = self.cache[i]
if not (cl and cl.init_state == state) then
self.cache[i] = self:tokenize_line(i, state)
end
end
self.cache.last_valid = max + 1
self.updated_highlighting = true
coroutine.yield()
end
end
end, self)
end
@ -127,28 +91,7 @@ end
function DocView:get_scrollable_size()
return self:get_line_height() * #self.doc.lines + style.padding.y * 2
end
function DocView:tokenize_line(idx, state)
local cl = {}
cl.init_state = state
cl.text = self.doc.lines[idx]
cl.tokens, cl.state = highlighter.tokenize(self.syntax, cl.text, state)
return cl
end
function DocView:get_cached_line(idx)
local cl = self.cache[idx]
if not cl or cl.text ~= self.doc.lines[idx] then
local prev = self.cache[idx-1]
cl = self:tokenize_line(idx, prev and prev.state)
self.cache[idx] = cl
self.cache.last_valid = math.min(self.cache.last_valid, idx)
end
return cl
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
end
@ -249,21 +192,42 @@ function DocView:scroll_to_make_visible(line, col)
end
local function mouse_selection(doc, clicks, line1, col1, line2, col2)
local swap = line2 < line1 or line2 == line1 and col2 <= col1
if swap then
line1, col1, line2, col2 = line2, col2, line1, col1
end
if clicks == 2 then
line1, col1 = translate.start_of_word(doc, line1, col1)
line2, col2 = translate.end_of_word(doc, line2, col2)
elseif clicks == 3 then
if line2 == #doc.lines and doc.lines[#doc.lines] ~= "\n" then
doc:insert(math.huge, math.huge, "\n")
end
line1, col1, line2, col2 = line1, 1, line2 + 1, 1
end
if swap then
return line2, col2, line1, col1
end
return line1, col1, line2, col2
end
function DocView:on_mouse_pressed(button, x, y, clicks)
local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks)
if caught then
return
end
local line, col = self:resolve_screen_position(x, y)
if clicks == 2 then
local line1, col1 = translate.start_of_word(self.doc, line, col)
local line2, col2 = translate.end_of_word(self.doc, line, col)
self.doc:set_selection(line2, col2, line1, col1)
elseif clicks == 3 then
self.doc:set_selection(line + 1, 1, line, 1)
if keymap.modkeys["shift"] then
if clicks == 1 then
local line1, col1 = select(3, self.doc:get_selection())
local line2, col2 = self:resolve_screen_position(x, y)
self.doc:set_selection(line2, col2, line1, col1)
end
else
self.doc:set_selection(line, col)
self.mouse_selecting = true
local line, col = self:resolve_screen_position(x, y)
self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col))
self.mouse_selecting = { line, col, clicks = clicks }
end
self.blink_timer = 0
end
@ -279,16 +243,17 @@ function DocView:on_mouse_moved(x, y, ...)
end
if self.mouse_selecting then
local _, _, line2, col2 = self.doc:get_selection()
local line1, col1 = self:resolve_screen_position(x, y)
self.doc:set_selection(line1, col1, line2, col2)
local l1, c1 = self:resolve_screen_position(x, y)
local l2, c2 = table.unpack(self.mouse_selecting)
local clicks = self.mouse_selecting.clicks
self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2))
end
end
function DocView:on_mouse_released(button)
DocView.super.on_mouse_released(self, button)
self.mouse_selecting = false
self.mouse_selecting = nil
end
@ -308,16 +273,6 @@ function DocView:update()
self.last_line, self.last_col = line, col
end
if self.updated_highlighting then
self.updated_highlighting = false
core.redraw = true
end
if self.doc.filename ~= self.last_filename then
reset_syntax(self)
self.last_filename = self.doc.filename
end
-- update blink timer
if self == core.active_view and not self.mouse_selecting then
local n = blink_period / 2
@ -339,10 +294,9 @@ end
function DocView:draw_line_text(idx, x, y)
local cl = self:get_cached_line(idx)
local tx, ty = x, y + self:get_line_text_y_offset()
local font = self:get_font()
for _, type, text in highlighter.each_token(cl.tokens) do
for _, type, text in self.doc.highlighter:each_token(idx) do
local color = style.syntax[type]
tx = renderer.draw_text(font, text, tx, ty, color)
end
@ -355,9 +309,9 @@ function DocView:draw_line_body(idx, x, y)
-- draw selection if it overlaps this line
local line1, col1, line2, col2 = self.doc:get_selection(true)
if idx >= line1 and idx <= line2 then
local cl = self:get_cached_line(idx)
local text = self.doc.lines[idx]
if line1 ~= idx then col1 = 1 end
if line2 ~= idx then col2 = #cl.text + 1 end
if line2 ~= idx then col2 = #text + 1 end
local x1 = x + self:get_col_x_offset(idx, col1)
local x2 = x + self:get_col_x_offset(idx, col2)
local lh = self:get_line_height()
@ -391,7 +345,7 @@ function DocView:draw_line_gutter(idx, x, y)
color = style.line_number2
end
local yoffset = self:get_line_text_y_offset()
x = x + self.scroll.x
x = x + style.padding.x
renderer.draw_text(self:get_font(), idx, x, y + yoffset, color)
end
@ -406,7 +360,7 @@ function DocView:draw()
local lh = self:get_line_height()
local _, y = self:get_line_screen_position(minline)
local x = self:get_content_offset() + style.padding.x
local x = self.position.x
for i = minline, maxline do
self:draw_line_gutter(i, x, y)
y = y + lh

View File

@ -8,7 +8,6 @@ local RootView
local StatusView
local CommandView
local Doc
local View
local core = {}
@ -17,12 +16,17 @@ local function project_scan_thread()
local function diff_files(a, b)
if #a ~= #b then return true end
for i, v in ipairs(a) do
if b[i].filename ~= v.filename or b[i].type ~= v.type then
if b[i].filename ~= v.filename
or b[i].modified ~= v.modified then
return true
end
end
end
local function compare_file(a, b)
return a.filename < b.filename
end
local function get_files(path, t)
coroutine.yield()
t = t or {}
@ -31,24 +35,25 @@ local function project_scan_thread()
local dirs, files = {}, {}
for _, file in ipairs(all) do
if not file:find("^%.") then
local file = path .. PATHSEP .. file
if not common.match_pattern(file, config.ignore_files) then
local file = (path ~= "." and path .. PATHSEP or "") .. file
local info = system.get_file_info(file)
if info and info.size < size_limit then
table.insert(info.type == "dir" and dirs or files, file)
info.filename = file
table.insert(info.type == "dir" and dirs or files, info)
end
end
end
table.sort(dirs)
for _, dir in ipairs(dirs) do
table.insert(t, { filename = dir, type = "dir" })
get_files(dir, t)
table.sort(dirs, compare_file)
for _, f in ipairs(dirs) do
table.insert(t, f)
get_files(f.filename, t)
end
table.sort(files)
for _, file in ipairs(files) do
table.insert(t, { filename = file, type = "file" })
table.sort(files, compare_file)
for _, f in ipairs(files) do
table.insert(t, f)
end
return t
@ -57,7 +62,7 @@ local function project_scan_thread()
while true do
-- get project files and replace previous table if the new table is
-- different
local t = get_files(core.project_dir)
local t = get_files(".")
if diff_files(core.project_files, t) then
core.project_files = t
core.redraw = true
@ -73,23 +78,30 @@ function core.init()
command = require "core.command"
keymap = require "core.keymap"
RootView = require "core.rootview"
View = require "core.view"
StatusView = require "core.statusview"
CommandView = require "core.commandview"
Doc = require "core.doc"
local project_dir = EXEDIR
local files = {}
for i = 2, #ARGS do
local info = system.get_file_info(ARGS[i]) or {}
if info.type == "file" then
table.insert(files, system.absolute_path(ARGS[i]))
elseif info.type == "dir" then
project_dir = ARGS[i]
end
end
system.chdir(project_dir)
core.frame_start = 0
core.clip_rect_stack = {{ 0,0,0,0 }}
core.log_items = {}
core.docs = {}
core.threads = setmetatable({}, { __mode = "k" })
core.project_files = {}
core.project_dir = "."
local info = ARGS[2] and system.get_file_info(ARGS[2])
if info and info.type == "dir" then
core.project_dir = ARGS[2]:gsub("[\\/]$", "")
end
core.redraw = true
core.root_view = RootView()
core.command_view = CommandView()
@ -97,29 +109,45 @@ function core.init()
core.root_view.root_node:split("down", core.command_view, true)
core.root_view.root_node.b:split("down", core.status_view, true)
core.active_view = core.root_view.root_node.a.active_view
core.add_thread(project_scan_thread)
command.add_defaults()
local got_plugin_error = not core.load_plugins()
local got_user_error = not core.try(require, "user")
local got_project_error = not core.load_project_module()
for i = 2, #ARGS do
local filename = ARGS[i]
local info = system.get_file_info(filename)
if info and info.type == "file" then
core.root_view:open_doc(core.open_doc(filename))
end
for _, filename in ipairs(files) do
core.root_view:open_doc(core.open_doc(filename))
end
if got_plugin_error or got_user_error then
if got_plugin_error or got_user_error or got_project_error then
command.perform("core:open-log")
end
end
local temp_uid = (system.get_time() * 1000) % 0xffffffff
local temp_file_prefix = string.format(".lite_temp_%08x", temp_uid)
local temp_file_counter = 0
local function delete_temp_files()
for _, filename in ipairs(system.list_dir(EXEDIR)) do
if filename:find(temp_file_prefix, 1, true) == 1 then
os.remove(EXEDIR .. PATHSEP .. filename)
end
end
end
function core.temp_filename(ext)
temp_file_counter = temp_file_counter + 1
return EXEDIR .. PATHSEP .. temp_file_prefix
.. string.format("%06x", temp_file_counter) .. (ext or "")
end
function core.quit(force)
if force then
delete_temp_files()
os.exit()
end
local dirty_count = 0
@ -160,6 +188,20 @@ function core.load_plugins()
end
function core.load_project_module()
local filename = ".lite_project.lua"
if system.get_file_info(filename) then
return core.try(function()
local fn, err = loadfile(filename)
if not fn then error("Error when loading project module:\n\t" .. err) end
fn()
core.log_quiet("Loaded project module")
end)
end
return true
end
function core.reload_module(name)
local old = package.loaded[name]
package.loaded[name] = nil
@ -171,6 +213,15 @@ function core.reload_module(name)
end
function core.set_active_view(view)
assert(view, "Tried to set active view to nil")
if view ~= core.active_view then
core.last_active_view = core.active_view
core.active_view = view
end
end
function core.add_thread(f, weak_ref)
local key = weak_ref or #core.threads + 1
local fn = function() return core.try(f) end
@ -226,7 +277,7 @@ end
local function log(icon, icon_color, fmt, ...)
local text = string.format(fmt, ...):gsub("%s", " ")
local text = string.format(fmt, ...)
if icon then
core.status_view:show_message(icon, icon_color, text)
end
@ -260,7 +311,7 @@ end
function core.try(fn, ...)
local err
local ok, res = xpcall(fn, function(msg)
local item = core.error(msg)
local item = core.error("%s", msg)
item.info = debug.traceback(nil, 2):gsub("\t", "")
err = msg
end, ...)
@ -288,11 +339,17 @@ function core.on_event(type, ...)
elseif type == "mousewheel" then
core.root_view:on_mouse_wheel(...)
elseif type == "filedropped" then
local mx, my = core.root_view.mouse.x, core.root_view.mouse.y
local ok, doc = core.try(core.open_doc, select(1, ...))
if ok then
core.root_view:on_mouse_pressed("left", mx, my, 1)
core.root_view:open_doc(doc)
local filename, mx, my = ...
local info = system.get_file_info(filename)
if info and info.type == "dir" then
system.exec(string.format("%q %q", EXEFILE, filename))
else
local ok, doc = core.try(core.open_doc, filename)
if ok then
local node = core.root_view.root_node:get_child_overlapping_point(mx, my)
node:set_active_view(node.active_view)
core.root_view:open_doc(doc)
end
end
elseif type == "quit" then
core.quit()
@ -303,7 +360,6 @@ end
function core.step()
-- handle events
local event_count = 0
local did_keymap = false
local mouse_moved = false
local mouse = { x = 0, y = 0, dx = 0, dy = 0 }
@ -316,12 +372,13 @@ function core.step()
elseif type == "textinput" and did_keymap then
did_keymap = false
else
did_keymap = core.on_event(type, a, b, c, d) or did_keymap
local _, res = core.try(core.on_event, type, a, b, c, d)
did_keymap = res or did_keymap
end
event_count = event_count + 1
core.redraw = true
end
if mouse_moved then
core.on_event("mousemoved", mouse.x, mouse.y, mouse.dx, mouse.dy)
core.try(core.on_event, "mousemoved", mouse.x, mouse.y, mouse.dx, mouse.dy)
end
local width, height = renderer.get_size()
@ -329,9 +386,7 @@ function core.step()
-- update
core.root_view.size.x, core.root_view.size.y = width, height
core.root_view:update()
if not (event_count > 0 or core.redraw) then
return
end
if not core.redraw then return false end
core.redraw = false
-- close unreferenced docs
@ -345,10 +400,10 @@ function core.step()
-- update window title
local name = core.active_view:get_name()
if name ~= "---" then
system.set_window_title(name .. " - lite")
else
system.set_window_title("lite")
local title = (name ~= "---") and (name .. " - lite") or "lite"
if title ~= core.window_title then
system.set_window_title(title)
core.window_title = title
end
-- draw
@ -357,6 +412,7 @@ function core.step()
renderer.set_clip_rect(table.unpack(core.clip_rect_stack[1]))
core.root_view:draw()
renderer.end_frame()
return true
end
@ -395,8 +451,11 @@ end)
function core.run()
while true do
core.frame_start = system.get_time()
core.step()
local did_redraw = core.step()
run_threads()
if not did_redraw and not system.window_has_focus() then
system.wait_event(0.25)
end
local elapsed = system.get_time() - core.frame_start
system.sleep(math.max(0, 1 / config.fps - elapsed))
end

View File

@ -84,8 +84,8 @@ end
keymap.add {
["ctrl+shift+p"] = "core:do-command",
["ctrl+p"] = "core:open-project-file",
["ctrl+shift+p"] = "core:find-command",
["ctrl+p"] = "core:find-file",
["ctrl+o"] = "core:open-file",
["ctrl+n"] = "core:new-doc",
["alt+return"] = "core:toggle-fullscreen",
@ -127,20 +127,24 @@ keymap.add {
["ctrl+x"] = "doc:cut",
["ctrl+c"] = "doc:copy",
["ctrl+v"] = "doc:paste",
["escape"] = { "command:escape" },
["escape"] = { "command:escape", "doc:select-none" },
["tab"] = { "command:complete", "doc:indent" },
["shift+tab"] = "doc:unindent",
["backspace"] = "doc:backspace",
["shift+backspace"] = "doc:backspace",
["ctrl+backspace"] = "doc:delete-to-previous-word-boundary",
["ctrl+shift+backspace"] = "doc:delete-to-previous-word-boundary",
["ctrl+backspace"] = "doc:delete-to-previous-word-start",
["ctrl+shift+backspace"] = "doc:delete-to-previous-word-start",
["delete"] = "doc:delete",
["shift+delete"] = "doc:delete",
["ctrl+delete"] = "doc:delete-to-next-word-end",
["ctrl+shift+delete"] = "doc:delete-to-next-word-end",
["return"] = { "command:submit", "doc:newline" },
["keypad enter"] = { "command:submit", "doc:newline" },
["ctrl+return"] = "doc:newline-below",
["ctrl+shift+return"] = "doc:newline-above",
["ctrl+j"] = "doc:join-lines",
["ctrl+a"] = "doc:select-all",
["ctrl+d"] = "doc:select-word",
["ctrl+d"] = { "find-replace:select-next", "doc:select-word" },
["ctrl+l"] = "doc:select-lines",
["ctrl+/"] = "doc:toggle-line-comments",
["ctrl+up"] = "doc:move-lines-up",
@ -152,10 +156,10 @@ keymap.add {
["right"] = "doc:move-to-next-char",
["up"] = { "command:select-previous", "doc:move-to-previous-line" },
["down"] = { "command:select-next", "doc:move-to-next-line" },
["ctrl+left"] = "doc:move-to-previous-word-boundary",
["ctrl+right"] = "doc:move-to-next-word-boundary",
["ctrl+["] = "doc:move-to-previous-start-of-block",
["ctrl+]"] = "doc:move-to-next-start-of-block",
["ctrl+left"] = "doc:move-to-previous-word-start",
["ctrl+right"] = "doc:move-to-next-word-end",
["ctrl+["] = "doc:move-to-previous-block-start",
["ctrl+]"] = "doc:move-to-next-block-end",
["home"] = "doc:move-to-start-of-line",
["end"] = "doc:move-to-end-of-line",
["ctrl+home"] = "doc:move-to-start-of-doc",
@ -167,10 +171,10 @@ keymap.add {
["shift+right"] = "doc:select-to-next-char",
["shift+up"] = "doc:select-to-previous-line",
["shift+down"] = "doc:select-to-next-line",
["ctrl+shift+left"] = "doc:select-to-previous-word-boundary",
["ctrl+shift+right"] = "doc:select-to-next-word-boundary",
["ctrl+shift+["] = "doc:select-to-previous-start-of-block",
["ctrl+shift+]"] = "doc:select-to-next-start-of-block",
["ctrl+shift+left"] = "doc:select-to-previous-word-start",
["ctrl+shift+right"] = "doc:select-to-next-word-end",
["ctrl+shift+["] = "doc:select-to-previous-block-start",
["ctrl+shift+]"] = "doc:select-to-next-block-end",
["shift+home"] = "doc:select-to-start-of-line",
["shift+end"] = "doc:select-to-end-of-line",
["ctrl+shift+home"] = "doc:select-to-start-of-doc",

View File

@ -1,6 +1,4 @@
local core = require "core"
local common = require "core.common"
local config = require "core.config"
local style = require "core.style"
local View = require "core.view"
@ -10,7 +8,7 @@ local LogView = View:extend()
function LogView:new()
LogView.super.new(self)
self.last_item = 0
self.last_item = core.log_items[#core.log_items]
self.scrollable = true
self.yoffset = 0
end
@ -29,13 +27,24 @@ function LogView:update()
self.yoffset = -(style.font:get_height() + style.padding.y)
end
self.scroll.to.y = math.max(0, self.scroll.to.y)
self:move_towards("yoffset", 0)
LogView.super.update(self)
end
local function draw_text_multiline(font, text, x, y, color)
local th = font:get_height()
local resx, resy = x, y
for line in text:gmatch("[^\n]+") do
resy = y
resx = renderer.draw_text(style.font, line, x, y, color)
y = y + th
end
return resx, resy
end
function LogView:draw()
self:draw_background(style.background)
@ -50,14 +59,12 @@ function LogView:draw()
x = renderer.draw_text(style.font, time, x, y, style.dim)
x = x + style.padding.x
local subx = x
x = renderer.draw_text(style.font, item.text, x, y, style.text)
x = renderer.draw_text(style.font, " at " .. item.at, x, y, style.dim)
x, y = draw_text_multiline(style.font, item.text, x, y, style.text)
renderer.draw_text(style.font, " at " .. item.at, x, y, style.dim)
y = y + th
if item.info then
for line in item.info:gmatch("[^\n]+") do
renderer.draw_text(style.font, line, subx, y, style.dim)
y = y + th
end
subx, y = draw_text_multiline(style.font, item.info, subx, y, style.dim)
y = y + th
end
y = y + style.padding.y
end

View File

@ -9,22 +9,33 @@ local DocView = require "core.docview"
local EmptyView = View:extend()
function EmptyView:draw()
self:draw_background(style.background)
local pos = self.position
local x, y, w, h = pos.x, pos.y, self.size.x, self.size.y
local _, y = common.draw_text(style.big_font, style.dim, "empty", "center", x, y, w, h)
local function draw_text(x, y, color)
local th = style.big_font:get_height()
local dh = th + style.padding.y * 2
x = renderer.draw_text(style.big_font, "lite", x, y + (dh - th) / 2, color)
x = x + style.padding.x
renderer.draw_rect(x, y, math.ceil(1 * SCALE), dh, color)
local lines = {
{ fmt = "%s to run a command", cmd = "core:do-command" },
{ fmt = "%s to open a file from the project", cmd = "core:open-project-file" },
{ fmt = "%s to run a command", cmd = "core:find-command" },
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
}
local th = style.font:get_height()
th = style.font:get_height()
y = y + (dh - th * 2 - style.padding.y) / 2
local w = 0
for _, line in ipairs(lines) do
local text = string.format(line.fmt, keymap.get_binding(line.cmd))
y = y + style.padding.y
common.draw_text(style.font, style.dim, text, "center", x, y, w, th)
y = y + th
w = math.max(w, renderer.draw_text(style.font, text, x + style.padding.x, y, color))
y = y + th + style.padding.y
end
return w, dh
end
function EmptyView:draw()
self:draw_background(style.background)
local w, h = draw_text(0, 0, { 0, 0, 0, 0 })
local x = self.position.x + math.max(style.padding.x, (self.size.x - w) / 2)
local y = self.position.y + (self.size.y - h) / 2
draw_text(x, y, style.dim)
end
@ -77,17 +88,18 @@ end
local type_map = { up="vsplit", down="vsplit", left="hsplit", right="hsplit" }
function Node:split(dir, view, locked)
assert(self.type == "leaf", "tried to split non-leaf node")
local type = assert(type_map[dir], "invalid direction")
assert(self.type == "leaf", "Tried to split non-leaf node")
local type = assert(type_map[dir], "Invalid direction")
local last_active = core.active_view
local child = Node()
child:consume(self)
self:consume(Node(type))
self.a = child
self.b = Node()
self.b.locked = locked
if view then self.b:add_view(view) end
if not self.b.active_view.focusable then
self.a:set_active_view(self.a.active_view)
if locked then
self.b.locked = locked
core.set_active_view(last_active)
end
if dir == "up" or dir == "left" then
self.a, self.b = self.b, self.a
@ -118,13 +130,15 @@ function Node:close_active_view(root)
p:set_active_view(p.active_view)
end
end
core.last_active_view = nil
end
self.active_view:try_close(do_close)
end
function Node:add_view(view)
assert(self.type == "leaf", "tried to add view to non-leaf node")
assert(self.type == "leaf", "Tried to add view to non-leaf node")
assert(not self.locked, "Tried to add view to locked node")
if self.views[1] and self.views[1]:is(EmptyView) then
table.remove(self.views)
end
@ -134,9 +148,9 @@ end
function Node:set_active_view(view)
assert(self.type == "leaf", "tried to set active view on non-leaf node")
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
self.active_view = view
core.active_view = view
core.set_active_view(view)
end
@ -241,7 +255,9 @@ function Node:get_locked_size()
local x1, y1 = self.a:get_locked_size()
local x2, y2 = self.b:get_locked_size()
if x1 and x2 then
return x1 + x2 + style.divider_size, y1 + y2 + style.divider_size
local dsx = (x1 < 1 or x2 < 1) and 0 or style.divider_size
local dsy = (y1 < 1 or y2 < 1) and 0 or style.divider_size
return x1 + x2 + dsx, y1 + y2 + dsy
end
end
end
@ -257,11 +273,11 @@ end
-- axis are swapped; this function lets us use the same code for both
local function calc_split_sizes(self, x, y, x1, x2)
local n
local ds = (x1 == 0 or x2 == 0) and 0 or style.divider_size
local ds = (x1 and x1 < 1 or x2 and x2 < 1) and 0 or style.divider_size
if x1 then
n = math.floor(x1 + ds)
n = x1 + ds
elseif x2 then
n = math.floor(self.size[x] - x2)
n = self.size[x] - x2
else
n = math.floor(self.size[x] * self.divider)
end
@ -333,7 +349,9 @@ function Node:draw_tabs()
color = style.text
end
core.push_clip_rect(x, y, w, h)
common.draw_text(style.font, color, text, "center", x, y, w, h)
x, w = x + style.padding.x, w - style.padding.x * 2
local align = style.font:get_width(text) > w and "left" or "center"
common.draw_text(style.font, color, text, align, x, y, w, h)
core.pop_clip_rect()
end
@ -347,7 +365,7 @@ function Node:draw()
self:draw_tabs()
end
local pos, size = self.active_view.position, self.active_view.size
core.push_clip_rect(pos.x, pos.y, size.x, size.y)
core.push_clip_rect(pos.x, pos.y, size.x + pos.x % 1, size.y + pos.y % 1)
self.active_view:draw()
core.pop_clip_rect()
else
@ -375,13 +393,16 @@ end
function RootView:get_active_node()
local node = self.root_node:get_node_for_view(core.active_view)
return node or self.root_node.a
return self.root_node:get_node_for_view(core.active_view)
end
function RootView:open_doc(doc)
local node = self:get_active_node()
if node.locked and core.last_active_view then
core.set_active_view(core.last_active_view)
node = self:get_active_node()
end
assert(not node.locked, "Cannot open doc on locked node")
for i, view in ipairs(node.views) do
if view.doc == doc then
@ -411,9 +432,7 @@ function RootView:on_mouse_pressed(button, x, y, clicks)
node:close_active_view(self.root_node)
end
else
if node.active_view.focusable then
core.active_view = node.active_view
end
core.set_active_view(node.active_view)
node.active_view:on_mouse_pressed(button, x, y, clicks)
end
end
@ -429,13 +448,13 @@ end
function RootView:on_mouse_moved(x, y, dx, dy)
if self.dragged_divider then
local div = self.dragged_divider
if div.type == "hsplit" then
div.divider = div.divider + dx / div.size.x
local node = self.dragged_divider
if node.type == "hsplit" then
node.divider = node.divider + dx / node.size.x
else
div.divider = div.divider + dy / div.size.y
node.divider = node.divider + dy / node.size.y
end
div.divider = common.clamp(div.divider, 0.01, 0.99)
node.divider = common.clamp(node.divider, 0.01, 0.99)
return
end

View File

@ -1,29 +1,39 @@
local core = require "core"
local common = require "core.common"
local command = require "core.command"
local config = require "core.config"
local style = require "core.style"
local DocView = require "core.docview"
local LogView = require "core.logview"
local View = require "core.view"
local StatusView = View:extend()
local separator = " "
local separator2 = " | "
StatusView.separator = " "
StatusView.separator2 = " | "
function StatusView:new()
StatusView.super.new(self)
self.focusable = false
self.message_timeout = 0
self.message = {}
end
function StatusView:on_mouse_pressed()
core.set_active_view(core.last_active_view)
if system.get_time() < self.message_timeout
and not core.active_view:is(LogView) then
command.perform "core:open-log"
end
end
function StatusView:show_message(icon, icon_color, text)
self.message = {
icon_color, style.icon_font, icon,
style.dim, style.font, separator2, style.text, text
style.dim, style.font, StatusView.separator2, style.text, text
}
self.message_timeout = system.get_time() + config.message_timeout
end
@ -42,93 +52,89 @@ function StatusView:update()
end
function StatusView:draw_items(items, right_align, yoffset)
local function draw_items(self, items, x, y, draw_fn)
local font = style.font
local color = style.text
local x, y = self:get_content_offset()
y = y + (yoffset or 0)
local i
if right_align then
x = x + self.size.x - style.padding.x
i = #items
else
x = x + style.padding.x
i = 1
end
while items[i] do
local item = items[i]
for _, item in ipairs(items) do
if type(item) == "userdata" then
font = item
elseif type(item) == "table" then
color = item
else
if right_align then
x = x - font:get_width(item)
common.draw_text(font, color, item, nil, x, y, 0, self.size.y)
else
x = common.draw_text(font, color, item, nil, x, y, 0, self.size.y)
end
x = draw_fn(font, color, item, nil, x, y, 0, self.size.y)
end
end
i = i + (right_align and -1 or 1)
return x
end
local function text_width(font, _, text, _, x)
return x + font:get_width(text)
end
function StatusView:draw_items(items, right_align, yoffset)
local x, y = self:get_content_offset()
y = y + (yoffset or 0)
if right_align then
local w = draw_items(self, items, 0, 0, text_width)
x = x + self.size.x - w - style.padding.x
draw_items(self, items, x, y, common.draw_text)
else
x = x + style.padding.x
draw_items(self, items, x, y, common.draw_text)
end
end
local function draw_for_doc_view(self, x, y)
local dv = core.active_view
local line, col = dv.doc:get_selection()
local dirty = dv.doc:is_dirty()
function StatusView:get_items()
if getmetatable(core.active_view) == DocView then
local dv = core.active_view
local line, col = dv.doc:get_selection()
local dirty = dv.doc:is_dirty()
self:draw_items {
dirty and style.accent or style.text, style.icon_font, "f",
style.dim, style.font, separator2, style.text,
dv.doc.filename and style.text or style.dim, dv.doc:get_name(),
style.text,
separator,
"line: ", line,
separator,
col > config.line_limit and style.accent or style.text, "col: ", col,
style.text,
separator,
string.format("%d%%", line / #dv.doc.lines * 100),
return {
dirty and style.accent or style.text, style.icon_font, "f",
style.dim, style.font, self.separator2, style.text,
dv.doc.filename and style.text or style.dim, dv.doc:get_name(),
style.text,
self.separator,
"line: ", line,
self.separator,
col > config.line_limit and style.accent or style.text, "col: ", col,
style.text,
self.separator,
string.format("%d%%", line / #dv.doc.lines * 100),
}, {
style.icon_font, "g",
style.font, style.dim, self.separator2, style.text,
#dv.doc.lines, " lines",
self.separator,
dv.doc.crlf and "CRLF" or "LF"
}
end
return {}, {
style.icon_font, "g",
style.font, style.dim, self.separator2,
#core.docs, style.text, " / ",
#core.project_files, " files"
}
self:draw_items({
"g", style.icon_font,
style.text, separator2, style.dim, style.font,
#dv.doc.lines, " lines",
separator,
dv.doc.crlf and "CRLF" or "LF"
}, true)
end
function StatusView:draw()
self:draw_background(style.background2)
local th = style.font:get_height()
local x, y = self:get_content_offset()
x = x + style.padding.x
y = y + (self.size.y - th) / 2
if self.message then
self:draw_items(self.message, false, self.size.y)
end
if getmetatable(core.active_view) == DocView then
draw_for_doc_view(self)
else
self:draw_items({
"g", style.icon_font,
style.text, separator2, style.dim, style.font,
#core.docs, " / ", style.dim,
#core.project_files, " files"
}, true)
end
local left, right = self:get_items()
self:draw_items(left)
self:draw_items(right, true)
end

View File

@ -12,30 +12,30 @@ style.big_font = renderer.font.load(EXEDIR .. "/data/fonts/font.ttf", 34 * SCALE
style.icon_font = renderer.font.load(EXEDIR .. "/data/fonts/icons.ttf", 14 * SCALE)
style.code_font = renderer.font.load(EXEDIR .. "/data/fonts/monospace.ttf", 13.5 * SCALE)
style.background = { common.color "#1F1F2B" }
style.background2 = { common.color "#181821" }
style.background3 = { common.color "#181821" }
style.text = { common.color "#8989ab" }
style.caret = { common.color "#8585FF" }
style.accent = { common.color "#ccccff" }
style.dim = { common.color "#42425c" }
style.divider = { common.color "#15151C" }
style.selection = { common.color "#39394f" }
style.line_number = { common.color "#42425c" }
style.line_number2 = { common.color "#73739e" }
style.line_highlight = { common.color "#252533" }
style.scrollbar = { common.color "#323245" }
style.scrollbar2 = { common.color "#3b3b52" }
style.background = { common.color "#2e2e32" }
style.background2 = { common.color "#252529" }
style.background3 = { common.color "#252529" }
style.text = { common.color "#97979c" }
style.caret = { common.color "#93DDFA" }
style.accent = { common.color "#e1e1e6" }
style.dim = { common.color "#525257" }
style.divider = { common.color "#202024" }
style.selection = { common.color "#48484f" }
style.line_number = { common.color "#525259" }
style.line_number2 = { common.color "#83838f" }
style.line_highlight = { common.color "#343438" }
style.scrollbar = { common.color "#414146" }
style.scrollbar2 = { common.color "#4b4b52" }
style.syntax = {}
style.syntax["normal"] = { common.color "#F5F5F5" }
style.syntax["symbol"] = { common.color "#F5F5F5" }
style.syntax["comment"] = { common.color "#616C76" }
style.syntax["normal"] = { common.color "#e1e1e6" }
style.syntax["symbol"] = { common.color "#e1e1e6" }
style.syntax["comment"] = { common.color "#676b6f" }
style.syntax["keyword"] = { common.color "#E58AC9" }
style.syntax["keyword2"] = { common.color "#F77483" }
style.syntax["number"] = { common.color "#FFA94D" }
style.syntax["literal"] = { common.color "#FFA94D" }
style.syntax["string"] = { common.color "#F8C34C" }
style.syntax["string"] = { common.color "#f7c95c" }
style.syntax["operator"] = { common.color "#93DDFA" }
style.syntax["function"] = { common.color "#93DDFA" }

View File

@ -1,34 +1,29 @@
local syntax = {}
local common = require "core.common"
local syntax = {}
syntax.items = {}
local plain_text_syntax = { patterns = {}, symbols = {} }
local function matches_pattern(text, pattern)
if type(pattern) == "string" then
return text:find(pattern)
end
for _, p in ipairs(pattern) do
if matches_pattern(text, p) then return true end
end
return false
end
function syntax.add(t)
table.insert(syntax.items, t)
end
function syntax.get(filename)
local function find(string, field)
for i = #syntax.items, 1, -1 do
local t = syntax.items[i]
if matches_pattern(filename, t.files) then
if common.match_pattern(string, t[field] or {}) then
return t
end
end
return plain_text_syntax
end
function syntax.get(filename, header)
return find(filename, "files")
or find(header, "headers")
or plain_text_syntax
end

View File

@ -1,4 +1,4 @@
local highlighter = {}
local tokenizer = {}
local function push_token(t, type, text)
@ -38,10 +38,14 @@ local function find_non_escaped(text, pattern, offset, esc)
end
function highlighter.tokenize(syntax, text, state)
function tokenizer.tokenize(syntax, text, state)
local res = {}
local i = 1
if #syntax.patterns == 0 then
return { "normal", text }
end
while i <= #text do
-- continue trying to match the end pattern of a pair if we have a state set
if state then
@ -100,9 +104,9 @@ local function iter(t, i)
end
end
function highlighter.each_token(t)
function tokenizer.each_token(t)
return iter, t, -1
end
return highlighter
return tokenizer

View File

@ -14,7 +14,6 @@ function View:new()
self.scroll = { x = 0, y = 0, to = { x = 0, y = 0 } }
self.cursor = "arrow"
self.scrollable = false
self.focusable = true
end
@ -45,13 +44,13 @@ end
function View:get_scrollable_size()
return 0
return math.huge
end
function View:get_scrollbar_rect()
local sz = self:get_scrollable_size()
if sz <= self.size.y then
if sz <= self.size.y or sz == math.huge then
return 0, 0, 0, 0
end
local h = math.max(20, self.size.y * self.size.y / sz)
@ -111,11 +110,20 @@ end
function View:get_content_offset()
return self.position.x - self.scroll.x, self.position.y - self.scroll.y
local x = common.round(self.position.x - self.scroll.x)
local y = common.round(self.position.y - self.scroll.y)
return x, y
end
function View:clamp_scroll_position()
local max = self:get_scrollable_size() - self.size.y
self.scroll.to.y = common.clamp(self.scroll.to.y, 0, max)
end
function View:update()
self:clamp_scroll_position()
self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3)
self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3)
end
@ -124,7 +132,7 @@ end
function View:draw_background(color)
local x, y = self.position.x, self.position.y
local w, h = self.size.x, self.size.y
renderer.draw_rect(x, y, math.ceil(w), math.ceil(h), color)
renderer.draw_rect(x, y, w + x % 1, h + y % 1, color)
end

View File

@ -8,9 +8,22 @@ local translate = require "core.doc.translate"
local RootView = require "core.rootview"
local DocView = require "core.docview"
local max_suggestions = 6
config.autocomplete_max_suggestions = 6
local symbols = {}
local autocomplete = {}
autocomplete.map = {}
local mt = { __tostring = function(t) return t.text end }
function autocomplete.add(t)
local items = {}
for text, info in pairs(t.items) do
info = (type(info) == "string") and info
table.insert(items, setmetatable({ text = text, info = info }, mt))
end
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
end
core.add_thread(function()
@ -35,8 +48,9 @@ core.add_thread(function()
end
while true do
local symbols = {}
-- lift all symbols from all docs
local t = {}
for _, doc in ipairs(core.docs) do
-- update the cache if the doc has changed since the last iteration
if not cache_is_valid(doc) then
@ -47,16 +61,13 @@ core.add_thread(function()
end
-- update symbol set with doc's symbol set
for sym in pairs(cache[doc].symbols) do
t[sym] = true
symbols[sym] = true
end
coroutine.yield()
end
-- update symbols list
symbols = {}
for sym in pairs(t) do
table.insert(symbols, sym)
end
autocomplete.add { name = "open-docs", items = symbols }
-- wait for next scan
local valid = true
@ -76,7 +87,6 @@ end)
local partial = ""
local suggestions_idx = 1
local suggestions = {}
local last_active_view
local last_line, last_col
@ -86,6 +96,33 @@ local function reset_suggestions()
end
local function update_suggestions()
local doc = core.active_view.doc
local filename = doc and doc.filename or ""
-- get all relevant suggestions for given filename
local items = {}
for _, v in pairs(autocomplete.map) do
if common.match_pattern(filename, v.files) then
for _, item in pairs(v.items) do
table.insert(items, item)
end
end
end
-- fuzzy match, remove duplicates and store
items = common.fuzzy_match(items, partial)
local j = 1
for i = 1, config.autocomplete_max_suggestions do
suggestions[i] = items[j]
while items[j] and items[i].text == items[j].text do
items[i].info = items[i].info or items[j].info
j = j + 1
end
end
end
local function get_partial_symbol()
local doc = core.active_view.doc
local line2, col2 = doc:get_selection()
@ -96,7 +133,6 @@ end
local function get_active_view()
if getmetatable(core.active_view) == DocView then
last_active_view = core.active_view
return core.active_view
end
end
@ -115,8 +151,12 @@ local function get_suggestions_rect(av)
local th = font:get_height()
local max_width = 0
for i, sym in ipairs(suggestions) do
max_width = math.max(max_width, font:get_width(sym))
for _, s in ipairs(suggestions) do
local w = font:get_width(s.text)
if s.info then
w = w + style.font:get_width(s.info) + style.padding.x
end
max_width = math.max(max_width, w)
end
return
@ -134,12 +174,16 @@ local function draw_suggestions_box(av)
-- draw text
local font = av:get_font()
local th = font:get_height()
local x, y = rx + style.padding.x, ry + style.padding.y
for i, sym in ipairs(suggestions) do
local lh = font:get_height() + style.padding.y
local y = ry + style.padding.y / 2
for i, s in ipairs(suggestions) do
local color = (i == suggestions_idx) and style.accent or style.text
renderer.draw_text(font, sym, x, y, color)
y = y + th + style.padding.y
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
if s.info then
color = (i == suggestions_idx) and style.text or style.dim
common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh)
end
y = y + lh
end
end
@ -158,10 +202,7 @@ RootView.on_text_input = function(...)
-- update partial symbol and suggestions
partial = get_partial_symbol()
if #partial >= 3 then
local t = common.fuzzy_match(symbols, partial)
for i = 1, max_suggestions do
suggestions[i] = t[i]
end
update_suggestions()
last_line, last_col = av.doc:get_selection()
else
reset_suggestions()
@ -211,7 +252,7 @@ command.add(predicate, {
["autocomplete:complete"] = function()
local doc = core.active_view.doc
local line, col = doc:get_selection()
local text = suggestions[suggestions_idx]
local text = suggestions[suggestions_idx].text
doc:insert(line, col, text)
doc:remove(line, col, line, col - #partial)
doc:set_selection(line, col + #text - #partial)
@ -238,3 +279,6 @@ keymap.add {
["down"] = "autocomplete:next",
["escape"] = "autocomplete:cancel",
}
return autocomplete

View File

@ -18,7 +18,7 @@ local function reload_doc(doc)
local sel = { doc:get_selection() }
doc:remove(1, 1, math.huge, math.huge)
doc:insert(1, 1, text:gsub("\n$", ""))
doc:insert(1, 1, text:gsub("\r", ""):gsub("\n$", ""))
doc:set_selection(table.unpack(sel))
update_time(doc)

View File

@ -1,27 +0,0 @@
local core = require "core"
local command = require "core.command"
local function exec(cmd)
local fp = io.popen(cmd, "r")
local res = fp:read("*a")
fp:close()
return res:gsub("%\n$", "")
end
command.add("core.docview", {
["exec:insert"] = function()
core.command_view:enter("Insert Result Of Command", function(cmd)
core.active_view.doc:text_input(exec(cmd))
end)
end,
["exec:replace"] = function()
core.command_view:enter("Replace With Result Of Command", function(cmd)
core.active_view.doc:replace(function(str)
return exec(string.format("echo %q | %s", str, cmd))
end)
end)
end,
})

View File

@ -8,6 +8,7 @@ syntax.add {
{ pattern = { "/%*", "%*/" }, type = "comment" },
{ pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = { "`", "`", '\\' }, type = "string" },
{ pattern = "0x[%da-fA-F]+", type = "number" },
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
{ pattern = "-?%.?%d+", type = "number" },
@ -16,30 +17,51 @@ syntax.add {
{ pattern = "[%a_][%w_]*", type = "symbol" },
},
symbols = {
["if"] = "keyword",
["then"] = "keyword",
["else"] = "keyword",
["do"] = "keyword",
["while"] = "keyword",
["for"] = "keyword",
["break"] = "keyword",
["continue"] = "keyword",
["return"] = "keyword",
["switch"] = "keyword",
["case"] = "keyword",
["const"] = "keyword",
["try"] = "keyword",
["catch"] = "keyword",
["throw"] = "keyword",
["var"] = "keyword",
["let"] = "keyword",
["get"] = "keyword",
["set"] = "keyword",
["function"] = "keyword",
["new"] = "keyword",
["this"] = "keyword2",
["true"] = "literal",
["false"] = "literal",
["null"] = "literal",
["async"] = "keyword",
["await"] = "keyword",
["break"] = "keyword",
["case"] = "keyword",
["catch"] = "keyword",
["class"] = "keyword",
["const"] = "keyword",
["continue"] = "keyword",
["debugger"] = "keyword",
["default"] = "keyword",
["delete"] = "keyword",
["do"] = "keyword",
["else"] = "keyword",
["export"] = "keyword",
["extends"] = "keyword",
["finally"] = "keyword",
["for"] = "keyword",
["function"] = "keyword",
["get"] = "keyword",
["if"] = "keyword",
["import"] = "keyword",
["in"] = "keyword",
["instanceof"] = "keyword",
["let"] = "keyword",
["new"] = "keyword",
["return"] = "keyword",
["set"] = "keyword",
["static"] = "keyword",
["super"] = "keyword",
["switch"] = "keyword",
["throw"] = "keyword",
["try"] = "keyword",
["typeof"] = "keyword",
["var"] = "keyword",
["void"] = "keyword",
["while"] = "keyword",
["with"] = "keyword",
["yield"] = "keyword",
["true"] = "literal",
["false"] = "literal",
["null"] = "literal",
["undefined"] = "literal",
["arguments"] = "keyword2",
["Infinity"] = "keyword2",
["NaN"] = "keyword2",
["this"] = "keyword2",
},
}

View File

@ -2,16 +2,18 @@ local syntax = require "core.syntax"
syntax.add {
files = "%.lua$",
headers = "^#!.*[ /]lua",
comment = "--",
patterns = {
{ pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = { "%[%[", "%]%]" }, type = "string" },
{ pattern = { "--%[%[", "%]%]"}, type = "comment" },
{ pattern = { "%-%-%[%[", "%]%]"}, type = "comment" },
{ pattern = "%-%-.-\n", type = "comment" },
{ pattern = "-?0x%x+", type = "number" },
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
{ pattern = "-?%.?%d+", type = "number" },
{ pattern = "<%a+>", type = "keyword2" },
{ pattern = "%.%.%.?", type = "operator" },
{ pattern = "[<>~=]=", type = "operator" },
{ pattern = "[%+%-=/%*%^%%#<>]", type = "operator" },

View File

@ -6,6 +6,7 @@ syntax.add {
{ pattern = "\\.", type = "normal" },
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
{ pattern = { "```", "```" }, type = "string" },
{ pattern = { "``", "``", "\\" }, type = "string" },
{ pattern = { "`", "`", "\\" }, type = "string" },
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
{ pattern = "%-%-%-+", type = "comment" },
@ -13,7 +14,7 @@ syntax.add {
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
{ pattern = "#.-\n", type = "keyword" },
{ pattern = "!?%[.*%]%(.*%)", type = "function" },
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
{ pattern = "https?://%S+", type = "function" },
},
symbols = { },

View File

@ -1,7 +1,8 @@
local syntax = require "core.syntax"
syntax.add {
files = "%.py$",
files = { "%.py$", "%.pyw$" },
headers = "^#!.*[ /]python",
comment = "#",
patterns = {
{ pattern = { "#", "\n" }, type = "comment" },

View File

@ -2,6 +2,7 @@ local syntax = require "core.syntax"
syntax.add {
files = { "%.xml$", "%.html?$" },
headers = "<%?xml",
patterns = {
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
{ pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },

View File

@ -12,6 +12,7 @@ local ResultsView = View:extend()
function ResultsView:new(text, fn)
ResultsView.super.new(self)
self.scrollable = true
self.brightness = 0
self:begin_search(text, fn)
end
@ -40,6 +41,7 @@ end
function ResultsView:begin_search(text, fn)
self.search_args = { text, fn }
self.results = {}
self.last_file_idx = 1
self.query = text
@ -54,8 +56,16 @@ function ResultsView:begin_search(text, fn)
self.last_file_idx = i
end
self.searching = false
self.brightness = 100
core.redraw = true
end, self)
end, self.results)
self.scroll.to.y = 0
end
function ResultsView:refresh()
self:begin_search(table.unpack(self.search_args))
end
@ -94,7 +104,7 @@ end
function ResultsView:update()
self.scroll.to.y = math.max(0, self.scroll.to.y)
self:move_towards("brightness", 0, 0.1)
ResultsView.super.update(self)
end
@ -110,7 +120,6 @@ end
function ResultsView:get_scrollable_size()
local rh = style.padding.y + style.font:get_height()
return self:get_results_yoffset() + #self.results * self:get_line_height()
end
@ -163,14 +172,16 @@ function ResultsView:draw()
text = string.format("Found %d matches for %q",
#self.results, self.query)
end
renderer.draw_text(style.font, text, x, y, style.text)
local color = common.lerp(style.text, style.accent, self.brightness / 100)
renderer.draw_text(style.font, text, x, y, color)
-- horizontal line
local yoffset = self:get_results_yoffset()
local x = ox + style.padding.x
local w = self.size.x - style.padding.x * 2
local h = style.divider_size
renderer.draw_rect(x, oy + yoffset - style.padding.y, w, h, style.dim)
local color = common.lerp(style.dim, style.text, self.brightness / 100)
renderer.draw_rect(x, oy + yoffset - style.padding.y, w, h, color)
if self.searching then
renderer.draw_rect(x, oy + yoffset - style.padding.y, w * per, h, style.text)
end
@ -245,9 +256,14 @@ command.add(ResultsView, {
["project-search:open-selected"] = function()
core.active_view:open_selected_result()
end,
["project-search:refresh"] = function()
core.active_view:refresh()
end,
})
keymap.add {
["f5"] = "project-search:refresh",
["ctrl+shift+f"] = "project-search:find",
["up"] = "project-search:select-previous",
["down"] = "project-search:select-next",

View File

@ -6,9 +6,7 @@ local keymap = require "core.keymap"
local style = require "core.style"
local View = require "core.view"
local TreeView = View:extend()
config.treeview_size = 200 * SCALE
local function get_depth(filename)
local n = 0
@ -19,11 +17,13 @@ local function get_depth(filename)
end
local TreeView = View:extend()
function TreeView:new()
TreeView.super.new(self)
self.scrollable = true
self.focusable = false
self.visible = true
self.init_size = true
self.cache = {}
end
@ -34,7 +34,7 @@ function TreeView:get_cached(item)
t = {}
t.filename = item.filename
t.abs_filename = system.absolute_path(item.filename)
t.path, t.name = t.filename:match("^(.*)[\\/](.+)$")
t.name = t.filename:match("[^\\/]+$")
t.depth = get_depth(t.filename)
t.type = item.type
self.cache[t.filename] = t
@ -124,11 +124,14 @@ end
function TreeView:update()
self.scroll.to.y = math.max(0, self.scroll.to.y)
-- update width
local dest = self.visible and config.treeview_size or 0
self:move_towards(self.size, "x", dest)
if self.init_size then
self.size.x = dest
self.init_size = false
else
self:move_towards(self.size, "x", dest)
end
TreeView.super.update(self)
end
@ -137,10 +140,8 @@ end
function TreeView:draw()
self:draw_background(style.background2)
local h = self:get_item_height()
local icon_width = style.icon_font:get_width("D")
local spacing = style.font:get_width(" ") * 2
local root_depth = get_depth(core.project_dir) + 1
local doc = core.active_view.doc
local active_filename = doc and system.absolute_path(doc.filename or "")
@ -160,7 +161,7 @@ function TreeView:draw()
end
-- icons
x = x + (item.depth - root_depth) * style.padding.x + style.padding.x
x = x + item.depth * style.padding.x + style.padding.x
if item.type == "dir" then
local icon1 = item.expanded and "-" or "+"
local icon2 = item.expanded and "D" or "d"
@ -184,7 +185,6 @@ end
-- init
local view = TreeView()
local node = core.root_view:get_active_node()
view.size.x = config.treeview_size
node:split("left", view, true)
-- register commands and keymap

View File

@ -4,9 +4,16 @@ local Doc = require "core.doc"
local function trim_trailing_whitespace(doc)
local cline, ccol = doc:get_selection()
for i = 1, #doc.lines do
local old_text = doc:get_text(i, 1, i, math.huge)
local new_text = old_text:gsub("%s*$", "")
-- don't remove whitespace which would cause the caret to reposition
if cline == i and ccol > #new_text then
new_text = old_text:sub(1, ccol - 1)
end
if old_text ~= new_text then
doc:insert(i, 1, new_text)
doc:remove(i, #new_text + 1, i, math.huge)
@ -17,7 +24,7 @@ end
command.add("core.docview", {
["trim-whitespace:trim-trailing-whitespace"] = function()
trim_trailing_whitespace(self.active_view.doc)
trim_trailing_whitespace(core.active_view.doc)
end,
})

View File

@ -5,7 +5,7 @@ style.background = { common.color "#fbfbfb" }
style.background2 = { common.color "#f2f2f2" }
style.background3 = { common.color "#f2f2f2" }
style.text = { common.color "#404040" }
style.caret = { common.color "#181818" }
style.caret = { common.color "#fc1785" }
style.accent = { common.color "#fc1785" }
style.dim = { common.color "#b0b0b0" }
style.divider = { common.color "#e8e8e8" }

146
doc/usage.md Normal file
View File

@ -0,0 +1,146 @@
# lite
![screenshot](https://user-images.githubusercontent.com/3920290/81471642-6c165880-91ea-11ea-8cd1-fae7ae8f0bc4.png)
## Overview
lite is a lightweight text editor written mostly in Lua — it aims to provide
something practical, pretty, *small* and fast, implemented as simply as
possible; easy to modify and extend, or to use without doing either.
## Getting Started
When lite is started it's typically opened with a *project directory* — this
is the directory where your project's code and other data resides. The project
directory is set once when lite is started and, for the duration of the
session, cannot be changed.
To open lite with a specific project directory the directory name can be passed
as a command-line argument *(`.` can be passed to use the current directory)* or
the directory can be dragged onto either the lite executable or a running
instance of lite.
The main way of opening files in lite is through the `core:find-file` command
— this provides a fuzzy finder over all of the project's files and can be
opened using the **`ctrl+p`** shortcut by default.
Commands can be run using keyboard shortcuts, or by using the `core:find-command`
command bound to **`ctrl+shift+p`** by default. For example, pressing
`ctrl+shift+p` and typing `newdoc` then pressing `return` would open a new
document. The current keyboard shortcut for a command can be seen to the right
of the command name on the command finder, thus to find the shortcut for a command
`ctrl+shift+p` can be pressed and the command name typed.
## User Module
lite can be configured through use of the user module. The user module can be
used for changing options in the config module, adding additional key bindings,
loading custom color themes, modifying the style or changing any other part of
lite to your personal preference.
The user module is loaded by lite when the application starts, after the plugins
have been loaded.
The user module can be modified by running the `core:open-user-module` command
or otherwise directly opening the `data/user/init.lua` file.
## Project Module
The project module is an optional module which is loaded from the current
project's directory when lite is started. Project modules can be useful for
things like adding custom commands for project-specific build systems, or
loading project-specific plugins.
The project module is loaded by lite when the application starts, after both the
plugins and user module have been loaded.
The project module can be edited by running the `core:open-project-module`
command — if the module does not exist for the current project when the
command is run it will be created.
## Commands
Commands in lite are used both through the command finder (`ctrl+shift+p`) and
by lite's keyboard shortcut system. Commands consist of 3 components:
* **Name** — The command name in the form of `namespace:action-name`, for
example: `doc:select-all`
* **Predicate** — A function that returns true if the command can be ran, for
example, for any document commands the predicate checks whether the active
view is a document
* **Function** — The function which performs the command itself
Commands can be added using the `command.add` function provided by the
`core.command` module:
```lua
local core = require "core"
local command = require "core.command"
command.add("core.docview", {
["doc:save"] = function()
core.active_view.doc:save()
core.log("Saved '%s', core.active_view.doc.filename)
end
})
```
Commands can be performed programatically (eg. from another command or by your
user module) by calling the `command.perform` function after requiring the
`command` module:
```lua
local command = require "core.command"
command.perform "core:quit"
```
## Keymap
All keyboard shortcuts in lite are handled by the `core.keymap` module. A key
binding in lite maps a "stroke" (eg. `ctrl+q`) to one or more commands (eg.
`core:quit`). When the shortcut is pressed lite will iterate each command
assigned to that key and run the *predicate function* for that command — if the
predicate passes it stops iterating and runs the command.
An example of where this used is the default binding of the `tab` key:
``` lua
["tab"] = { "command:complete", "doc:indent" },
```
When tab is pressed the `command:complete` command is attempted which will only
succeed if the command-input at the bottom of the window is active. Otherwise
the `doc:indent` command is attempted which will only succeed if we have a
document as our active view.
A new mapping can be added by your user module as follows:
```lua
local keymap = require "core.keymap"
keymap.add { ["ctrl+q"] = "core:quit" }
```
## Plugins
Plugins in lite are normal lua modules and are treated as such — no
complicated plugin manager is provided, and, once a plugin is loaded, it is never
expected be to have to unload itself.
To install a plugin simply drop it in the `data/plugins` directory — installed
plugins will be automatically loaded when lite starts. To uninstall a plugin the
plugin file can be deleted — any plugin (including those included with lite's
default installation) can be deleted to remove its functionality.
If you want to load a plugin only under a certain circumstance (for example,
only on a given project) the plugin can be placed somewhere other than the
`data/plugins` directory so that it is not automatically loaded. The plugin can
then be loaded manually as needed by using the `require` function.
Plugins can be downloaded from the [plugins repository](https://github.com/rxi/lite-plugins).
## Color Themes
Colors themes in lite are lua modules which overwrite the color fields of lite's
`core.style` module. Color themes should be placed in the `data/user/colors`
directory.
A color theme can be set by requiring it in your user module:
```lua
require "user.colors.winter"
```
Color themes can be downloaded from the [color themes repository](https://github.com/rxi/lite-colors).

BIN
icon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

1369
icon.inl vendored Normal file

File diff suppressed because it is too large Load Diff

49
lite.cbp Executable file
View File

@ -0,0 +1,49 @@
<?xml version="1.0" ?>
<CodeBench_Project name="lite" path="SDH3:Programming&#047;workspace&#047;MyProjects&#047;lite&#047;lite.cbp" created="1387049720" lastmodified="1387152257">
<plugin name="PROGDIR:Plugins&#047;AmigaOS4SDK.CCPlugin" flags="0"/>
<target/>
<homedir name="SDH3:Programming&#047;workspace&#047;MyProjects&#047;lite"/>
<includedir name="Applications:Programming&#047;workspace&#047;OtherProjects&#047;lite&#047;src"/>
<compiler name="gcc:bin&#047;gcc" switches="-D__USE_INLINE__ -Wall -Werror -Wwrite-strings" stack="131072"/>
<linker switches="-lauto"/>
<debugger name="SDK:c&#047;gdb"/>
<builder name="SDK:c&#047;make -f"/>
<environment/>
<headers>
<file name="Applications:Programming&#047;workspace&#047;OtherProjects&#047;lite&#047;src&#047;api&#047;api.h" open="0"/>
<file name="Applications:Programming&#047;workspace&#047;OtherProjects&#047;lite&#047;src&#047;rencache.h" open="0"/>
<file name="Applications:Programming&#047;workspace&#047;OtherProjects&#047;lite&#047;src&#047;lib&#047;stb&#047;stb_truetype.h" open="0"/>
<file name="Applications:Programming&#047;workspace&#047;OtherProjects&#047;lite&#047;src&#047;renderer.h" open="0"/>
</headers>
<sources>
<file name="src&#047;api&#047;api.c" open="0"/>
<file name="src&#047;api&#047;renderer.c" open="0"/>
<file name="src&#047;api&#047;renderer_font.c" open="0"/>
<file name="src&#047;api&#047;system.c" open="0"/>
<file name="src&#047;main.c" open="1" current="1" top="75" left="0" line="97" row="16"/>
<file name="src&#047;rencache.c" open="0"/>
<file name="src&#047;renderer.c" open="0"/>
<file name="src&#047;lib&#047;stb&#047;stb_truetype.c" open="0"/>
</sources>
<flags value="0x0000000000078005"/>
<buildscript name="Makefile" depth="3" open="0"/>
<projectnotes open="0"/>
<buildwindow open="0"/>
<targets>
<target name="lite" linker="gcc:bin&#047;gcc" switches="-llua -lSDL2 -lpthread -lauto" flags="0x00000002">
<file name="src&#047;lib&#047;stb&#047;stb_truetype.c"/>
<file name="src&#047;api&#047;renderer.c"/>
<file name="src&#047;api&#047;renderer_font.c"/>
<file name="src&#047;api&#047;system.c"/>
<file name="src&#047;main.c"/>
<file name="src&#047;api&#047;api.c"/>
<file name="src&#047;rencache.c"/>
<file name="src&#047;renderer.c"/>
</target>
</targets>
<includepath>
<include path="src"/>
</includepath>
<logfile name="RAM Disk:Build.log"/>
<search lastsearch="stbtt_Scale" sensecase="1" replace_state="0"/>
</CodeBench_Project>

BIN
lite.cbp.info Normal file

Binary file not shown.

BIN
lite.info Normal file

Binary file not shown.

View File

@ -1,7 +1,6 @@
#include "api.h"
#include "renderer.h"
#include "rencache.h"
#include "xalloc.h"
static RenColor checkcolor(lua_State *L, int idx, int def) {

View File

@ -2,9 +2,11 @@
#include <stdbool.h>
#include <ctype.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include "api.h"
#include "rencache.h"
#ifdef _WIN32
#include <windows.h>
#endif
@ -35,6 +37,7 @@ static char* key_name(char *dst, int sym) {
static int f_poll_event(lua_State *L) {
char buf[16];
int mx, my, wx, wy;
SDL_Event e;
top:
@ -54,7 +57,9 @@ top:
lua_pushnumber(L, e.window.data2);
return 3;
} else if (e.window.event == SDL_WINDOWEVENT_EXPOSED) {
SDL_UpdateWindowSurface(window);
rencache_invalidate();
lua_pushstring(L, "exposed");
return 1;
}
/* on some systems, when alt-tabbing to the window SDL will queue up
** several KEYDOWN events for the `tab` key; we flush all keydown
@ -65,10 +70,14 @@ top:
goto top;
case SDL_DROPFILE:
SDL_GetGlobalMouseState(&mx, &my);
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);
SDL_free(e.drop.file);
return 2;
return 4;
case SDL_KEYDOWN:
lua_pushstring(L, "keypressed");
@ -123,6 +132,13 @@ top:
}
static int f_wait_event(lua_State *L) {
double n = luaL_checknumber(L, 1);
lua_pushboolean(L, SDL_WaitEventTimeout(NULL, n * 1000));
return 1;
}
static SDL_Cursor* cursor_cache[SDL_SYSTEM_CURSOR_HAND + 1];
static const char *cursor_opts[] = {
@ -209,6 +225,14 @@ static int f_show_confirm_dialog(lua_State *L) {
}
static int f_chdir(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
int err = chdir(path);
if (err) { luaL_error(L, "chdir() failed"); }
return 0;
}
static int f_list_dir(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
@ -311,6 +335,24 @@ static int f_sleep(lua_State *L) {
}
static int f_exec(lua_State *L) {
size_t len;
const char *cmd = luaL_checklstring(L, 1, &len);
char *buf = malloc(len + 32);
if (!buf) { luaL_error(L, "buffer allocation failed"); }
#if _WIN32
sprintf(buf, "cmd /c \"%s\"", cmd);
WinExec(buf, SW_HIDE);
#else
sprintf(buf, "%s &", cmd);
int res = system(buf);
(void) res;
#endif
free(buf);
return 0;
}
static int f_fuzzy_match(lua_State *L) {
const char *str = luaL_checkstring(L, 1);
const char *ptn = luaL_checkstring(L, 2);
@ -321,11 +363,11 @@ static int f_fuzzy_match(lua_State *L) {
while (*str == ' ') { str++; }
while (*ptn == ' ') { ptn++; }
if (tolower(*str) == tolower(*ptn)) {
score += run;
score += run * 10 - (*str != *ptn);
run++;
ptn++;
} else {
score--;
score -= 10;
run = 0;
}
str++;
@ -339,11 +381,13 @@ static int f_fuzzy_match(lua_State *L) {
static const luaL_Reg lib[] = {
{ "poll_event", f_poll_event },
{ "wait_event", f_wait_event },
{ "set_cursor", f_set_cursor },
{ "set_window_title", f_set_window_title },
{ "set_window_mode", f_set_window_mode },
{ "window_has_focus", f_window_has_focus },
{ "show_confirm_dialog", f_show_confirm_dialog },
{ "chdir", f_chdir },
{ "list_dir", f_list_dir },
{ "absolute_path", f_absolute_path },
{ "get_file_info", f_get_file_info },
@ -351,6 +395,7 @@ static const luaL_Reg lib[] = {
{ "set_clipboard", f_set_clipboard },
{ "get_time", f_get_time },
{ "sleep", f_sleep },
{ "exec", f_exec },
{ "fuzzy_match", f_fuzzy_match },
{ NULL, NULL }
};

View File

@ -1,5 +1,14 @@
// stb_truetype.h - v1.19 - public domain
// authored from 2009-2016 by Sean Barrett / RAD Game Tools
// stb_truetype.h - v1.24 - public domain
// authored from 2009-2020 by Sean Barrett / RAD Game Tools
//
// =======================================================================
//
// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES
//
// This library does no range checking of the offsets found in the file,
// meaning an attacker can use it to read arbitrary memory.
//
// =======================================================================
//
// This library processes TrueType files:
// parse files
@ -32,11 +41,11 @@
// Daniel Ribeiro Maciel
//
// Bug/warning reports/fixes:
// "Zer" on mollyrocket Fabian "ryg" Giesen
// Cass Everitt Martins Mozeiko
// stoiko (Haemimont Games) Cap Petschulat
// Brian Hook Omar Cornut
// Walter van Niftrik github:aloucks
// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe
// Cass Everitt Martins Mozeiko github:aloucks
// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam
// Brian Hook Omar Cornut github:vassvik
// Walter van Niftrik Ryan Griege
// David Gow Peter LaValle
// David Given Sergey Popov
// Ivan-Assen Ivanov Giumo X. Clanjor
@ -44,11 +53,16 @@
// Johan Duparc Thomas Fields
// Hou Qiming Derek Vinyard
// Rob Loach Cort Stratton
// Kenney Phillis Jr. github:oyvindjam
// Brian Costabile github:vassvik
//
// Kenney Phillis Jr. Brian Costabile
// Ken Voskuil (kaesve)
//
// VERSION HISTORY
//
// 1.24 (2020-02-05) fix warning
// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS)
// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined
// 1.21 (2019-02-25) fix warning
// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics()
// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod
// 1.18 (2018-01-29) add missing function
// 1.17 (2017-07-23) make more arguments const; doc fix
@ -75,7 +89,7 @@
//
// USAGE
//
// Include this file in whatever places neeed to refer to it. In ONE C/C++
// Include this file in whatever places need to refer to it. In ONE C/C++
// file, write:
// #define STB_TRUETYPE_IMPLEMENTATION
// before the #include of this file. This expands out the actual
@ -206,7 +220,7 @@
//
// Advancing for the next character:
// Call GlyphHMetrics, and compute 'current_point += SF * advance'.
//
//
//
// ADVANCED USAGE
//
@ -242,19 +256,6 @@
// recommend it.
//
//
// SOURCE STATISTICS (based on v0.6c, 2050 LOC)
//
// Documentation & header file 520 LOC \___ 660 LOC documentation
// Sample code 140 LOC /
// Truetype parsing 620 LOC ---- 620 LOC TrueType
// Software rasterization 240 LOC \ .
// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation
// Bitmap management 100 LOC /
// Baked bitmap interface 70 LOC /
// Font name matching & access 150 LOC ---- 150
// C runtime library abstraction 60 LOC ---- 60
//
//
// PERFORMANCE MEASUREMENTS FOR 1.06:
//
// 32-bit 64-bit
@ -344,7 +345,7 @@ int main(int argc, char **argv)
}
return 0;
}
#endif
#endif
//
// Output:
//
@ -358,9 +359,9 @@ int main(int argc, char **argv)
// :@@. M@M
// @@@o@@@@
// :M@@V:@@.
//
//
//////////////////////////////////////////////////////////////////////////////
//
//
// Complete program: print "Hello World!" banner, with bugs
//
#if 0
@ -556,6 +557,8 @@ STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int p
//
// It's inefficient; you might want to c&p it and optimize it.
STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap);
// Query the font vertical metrics without having to create a font first.
//////////////////////////////////////////////////////////////////////////////
@ -641,6 +644,12 @@ STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h
// To use with PackFontRangesGather etc., you must set it before calls
// call to PackFontRangesGatherRects.
STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip);
// If skip != 0, this tells stb_truetype to skip any codepoints for which
// there is no corresponding glyph. If skip=0, which is the default, then
// codepoints without a glyph recived the font's "missing character" glyph,
// typically an empty box by convention.
STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above
int char_index, // character to display
float *xpos, float *ypos, // pointers to current position in screen pixel space
@ -653,7 +662,7 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, cons
// Calling these functions in sequence is roughly equivalent to calling
// stbtt_PackFontRanges(). If you more control over the packing of multiple
// fonts, or if you want to pack custom data into a font texture, take a look
// at the source to of stbtt_PackFontRanges() and create a custom version
// at the source to of stbtt_PackFontRanges() and create a custom version
// using these functions, e.g. call GatherRects multiple times,
// building up a single array of rects, then call PackRects once,
// then call RenderIntoRects repeatedly. This may result in a
@ -669,6 +678,7 @@ struct stbtt_pack_context {
int height;
int stride_in_bytes;
int padding;
int skip_missing;
unsigned int h_oversample, v_oversample;
unsigned char *pixels;
void *nodes;
@ -694,7 +704,7 @@ STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index);
// file will only define one font and it always be at offset 0, so it will
// return '0' for index 0, and -1 for all other indices.
// The following structure is defined publically so you can declare one on
// The following structure is defined publicly so you can declare one on
// the stack or as a global or etc, but you should treat it as opaque.
struct stbtt_fontinfo
{
@ -704,7 +714,7 @@ struct stbtt_fontinfo
int numGlyphs; // number of glyphs, needed for range checking
int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf
int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf
int index_map; // a cmap mapping for our chosen character encoding
int indexToLocFormat; // format needed to map from glyph index to glyph
@ -733,6 +743,7 @@ STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codep
// and you want a speed-up, call this function with the character you're
// going to process, then use glyph-based functions instead of the
// codepoint-based functions.
// Returns 0 if the character codepoint is not defined in the font.
//////////////////////////////////////////////////////////////////////////////
@ -786,6 +797,18 @@ STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1,
STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1);
// as above, but takes one or more glyph indices for greater efficiency
typedef struct stbtt_kerningentry
{
int glyph1; // use stbtt_FindGlyphIndex
int glyph2;
int advance;
} stbtt_kerningentry;
STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info);
STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length);
// Retrieves a complete list of all of the kerning pairs provided by the font
// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write.
// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1)
//////////////////////////////////////////////////////////////////////////////
//
@ -820,7 +843,7 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s
// returns # of vertices and fills *vertices with the pointer to them
// these are expressed in "unscaled" coordinates
//
// The shape is a series of countours. Each one starts with
// The shape is a series of contours. Each one starts with
// a STBTT_moveto, then consists of a series of mixed
// STBTT_lineto and STBTT_curveto segments. A lineto
// draws a line from previous endpoint to its x,y; a curveto
@ -830,6 +853,11 @@ STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, s
STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices);
// frees the data allocated above
STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg);
STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg);
// fills svg with the character's SVG data.
// returns data size or 0 if SVG not found.
//////////////////////////////////////////////////////////////////////////////
//
// BITMAP RENDERING
@ -916,7 +944,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff);
// These functions compute a discretized SDF field for a single character, suitable for storing
// in a single-channel texture, sampling with bilinear filtering, and testing against
// larger than some threshhold to produce scalable fonts.
// larger than some threshold to produce scalable fonts.
// info -- the font
// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap
// glyph/codepoint -- the character to generate the SDF for
@ -959,7 +987,7 @@ STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, floa
// and computing from that can allow drop-out prevention).
//
// The algorithm has not been optimized at all, so expect it to be slow
// if computing lots of characters or very large sizes.
// if computing lots of characters or very large sizes.
@ -1331,6 +1359,22 @@ static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict)
return stbtt__cff_get_index(&cff);
}
// since most people won't use this, find this table the first time it's needed
static int stbtt__get_svg(stbtt_fontinfo *info)
{
stbtt_uint32 t;
if (info->svg < 0) {
t = stbtt__find_table(info->data, info->fontstart, "SVG ");
if (t) {
stbtt_uint32 offset = ttULONG(info->data + t + 2);
info->svg = t + offset;
} else {
info->svg = 0;
}
}
return info->svg;
}
static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart)
{
stbtt_uint32 cmap, t;
@ -1410,6 +1454,8 @@ static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, in
else
info->numGlyphs = 0xffff;
info->svg = -1;
// find a cmap encoding table we understand *now* to avoid searching
// later. (todo: could make this installable)
// the same regardless of glyph.
@ -1716,7 +1762,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s
if (i != 0)
num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);
// now start the new one
// now start the new one
start_off = !(flags & 1);
if (start_off) {
// if we start off with an off-curve point, then when we need to find a point on the curve
@ -1758,7 +1804,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s
}
}
num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy);
} else if (numberOfContours == -1) {
} else if (numberOfContours < 0) {
// Compound shapes.
int more = 1;
stbtt_uint8 *comp = data + g + 10;
@ -1769,7 +1815,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s
int comp_num_verts = 0, i;
stbtt_vertex *comp_verts = 0, *tmp = 0;
float mtx[6] = {1,0,0,1,0,0}, m, n;
flags = ttSHORT(comp); comp+=2;
gidx = ttSHORT(comp); comp+=2;
@ -1799,7 +1845,7 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s
mtx[2] = ttSHORT(comp)/16384.0f; comp+=2;
mtx[3] = ttSHORT(comp)/16384.0f; comp+=2;
}
// Find transformation scales.
m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]);
n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]);
@ -1835,9 +1881,6 @@ static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, s
// More components ?
more = flags & (1<<5);
}
} else if (numberOfContours < 0) {
// @TODO other compound variations?
STBTT_assert(0);
} else {
// numberOfCounters == 0, do nothing
}
@ -2266,6 +2309,48 @@ STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_inde
}
}
STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info)
{
stbtt_uint8 *data = info->data + info->kern;
// we only look at the first table. it must be 'horizontal' and format 0.
if (!info->kern)
return 0;
if (ttUSHORT(data+2) < 1) // number of tables, need at least 1
return 0;
if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format
return 0;
return ttUSHORT(data+10);
}
STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length)
{
stbtt_uint8 *data = info->data + info->kern;
int k, length;
// we only look at the first table. it must be 'horizontal' and format 0.
if (!info->kern)
return 0;
if (ttUSHORT(data+2) < 1) // number of tables, need at least 1
return 0;
if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format
return 0;
length = ttUSHORT(data+10);
if (table_length < length)
length = table_length;
for (k = 0; k < length; k++)
{
table[k].glyph1 = ttUSHORT(data+18+(k*6));
table[k].glyph2 = ttUSHORT(data+20+(k*6));
table[k].advance = ttSHORT(data+22+(k*6));
}
return length;
}
static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2)
{
stbtt_uint8 *data = info->data + info->kern;
@ -2463,6 +2548,7 @@ static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, i
if (valueFormat2 != 0) return 0;
STBTT_assert(coverageIndex < pairSetCount);
STBTT__NOTUSED(pairSetCount);
needle=glyph2;
r=pairValueCount-1;
@ -2540,8 +2626,7 @@ STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int
if (info->gpos)
xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2);
if (info->kern)
else if (info->kern)
xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2);
return xAdvance;
@ -2602,6 +2687,45 @@ STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v)
STBTT_free(v, info->userdata);
}
STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl)
{
int i;
stbtt_uint8 *data = info->data;
stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info);
int numEntries = ttUSHORT(svg_doc_list);
stbtt_uint8 *svg_docs = svg_doc_list + 2;
for(i=0; i<numEntries; i++) {
stbtt_uint8 *svg_doc = svg_docs + (12 * i);
if ((gl >= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2)))
return svg_doc;
}
return 0;
}
STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg)
{
stbtt_uint8 *data = info->data;
stbtt_uint8 *svg_doc;
if (info->svg == 0)
return 0;
svg_doc = stbtt_FindSVGDoc(info, gl);
if (svg_doc != NULL) {
*svg = (char *) data + info->svg + ttULONG(svg_doc + 4);
return ttULONG(svg_doc + 8);
} else {
return 0;
}
}
STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg)
{
return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg);
}
//////////////////////////////////////////////////////////////////////////////
//
// antialiasing software rasterizer
@ -2727,7 +2851,7 @@ static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, i
float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);
STBTT_assert(z != NULL);
if (!z) return z;
// round dx down to avoid overshooting
if (dxdy < 0)
z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy);
@ -2805,7 +2929,7 @@ static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__ac
}
}
}
e = e->next;
}
}
@ -3160,7 +3284,13 @@ static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e,
if (e->y0 != e->y1) {
stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata);
if (z != NULL) {
STBTT_assert(z->ey >= scan_y_top);
if (j == 0 && off_y != 0) {
if (z->ey < scan_y_top) {
// this can happen due to subpixel positioning and some kind of fp rounding error i think
z->ey = scan_y_top;
}
}
STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds
// insert at front
z->next = active;
active = z;
@ -3229,7 +3359,7 @@ static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n)
static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n)
{
/* threshhold for transitioning to insertion sort */
/* threshold for transitioning to insertion sort */
while (n > 12) {
stbtt__edge t;
int c01,c12,c,m,i,j;
@ -3364,7 +3494,7 @@ static void stbtt__add_point(stbtt__point *points, int n, float x, float y)
points[n].y = y;
}
// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching
// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching
static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n)
{
// midpoint
@ -3527,7 +3657,7 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info
{
int ix0,iy0,ix1,iy1;
stbtt__bitmap gbm;
stbtt_vertex *vertices;
stbtt_vertex *vertices;
int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);
if (scale_x == 0) scale_x = scale_y;
@ -3550,7 +3680,7 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info
if (height) *height = gbm.h;
if (xoff ) *xoff = ix0;
if (yoff ) *yoff = iy0;
if (gbm.w && gbm.h) {
gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata);
if (gbm.pixels) {
@ -3561,7 +3691,7 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info
}
STBTT_free(vertices, info->userdata);
return gbm.pixels;
}
}
STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff)
{
@ -3573,7 +3703,7 @@ STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigne
int ix0,iy0;
stbtt_vertex *vertices;
int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices);
stbtt__bitmap gbm;
stbtt__bitmap gbm;
stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0);
gbm.pixels = output;
@ -3595,7 +3725,7 @@ STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *
STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff)
{
return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff);
}
}
STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint)
{
@ -3610,7 +3740,7 @@ STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, uns
STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff)
{
return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff);
}
}
STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint)
{
@ -3735,7 +3865,7 @@ static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *no
con->y = 0;
con->bottom_y = 0;
STBTT__NOTUSED(nodes);
STBTT__NOTUSED(num_nodes);
STBTT__NOTUSED(num_nodes);
}
static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects)
@ -3789,6 +3919,7 @@ STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, in
spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw;
spc->h_oversample = 1;
spc->v_oversample = 1;
spc->skip_missing = 0;
stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes);
@ -3814,6 +3945,11 @@ STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h
spc->v_oversample = v_oversample;
}
STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip)
{
spc->skip_missing = skip;
}
#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1)
static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width)
@ -3956,6 +4092,7 @@ static float stbtt__oversample_shift(int oversample)
STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
{
int i,j,k;
int missing_glyph_added = 0;
k=0;
for (i=0; i < num_ranges; ++i) {
@ -3967,13 +4104,19 @@ STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stb
int x0,y0,x1,y1;
int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];
int glyph = stbtt_FindGlyphIndex(info, codepoint);
stbtt_GetGlyphBitmapBoxSubpixel(info,glyph,
scale * spc->h_oversample,
scale * spc->v_oversample,
0,0,
&x0,&y0,&x1,&y1);
rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1);
rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1);
if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) {
rects[k].w = rects[k].h = 0;
} else {
stbtt_GetGlyphBitmapBoxSubpixel(info,glyph,
scale * spc->h_oversample,
scale * spc->v_oversample,
0,0,
&x0,&y0,&x1,&y1);
rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1);
rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1);
if (glyph == 0)
missing_glyph_added = 1;
}
++k;
}
}
@ -4007,7 +4150,7 @@ STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info
// rects array must be big enough to accommodate all characters in the given ranges
STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects)
{
int i,j,k, return_value = 1;
int i,j,k, missing_glyph = -1, return_value = 1;
// save current values
int old_h_over = spc->h_oversample;
@ -4026,7 +4169,7 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const
sub_y = stbtt__oversample_shift(spc->v_oversample);
for (j=0; j < ranges[i].num_chars; ++j) {
stbrp_rect *r = &rects[k];
if (r->was_packed) {
if (r->was_packed && r->w != 0 && r->h != 0) {
stbtt_packedchar *bc = &ranges[i].chardata_for_range[j];
int advance, lsb, x0,y0,x1,y1;
int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j];
@ -4072,6 +4215,13 @@ STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const
bc->yoff = (float) y0 * recip_v + sub_y;
bc->xoff2 = (x0 + r->w) * recip_h + sub_x;
bc->yoff2 = (y0 + r->h) * recip_v + sub_y;
if (glyph == 0)
missing_glyph = j;
} else if (spc->skip_missing) {
return_value = 0;
} else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) {
ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph];
} else {
return_value = 0; // if any fail, report failure
}
@ -4110,7 +4260,7 @@ STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char
n = 0;
for (i=0; i < num_ranges; ++i)
n += ranges[i].num_chars;
rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context);
if (rects == NULL)
return 0;
@ -4121,7 +4271,7 @@ STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char
n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects);
stbtt_PackFontRangesPackRects(spc, rects, n);
return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects);
STBTT_free(rects, spc->user_allocator_context);
@ -4140,6 +4290,19 @@ STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *
return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1);
}
STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap)
{
int i_ascent, i_descent, i_lineGap;
float scale;
stbtt_fontinfo info;
stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index));
scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size);
stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap);
*ascent = (float) i_ascent * scale;
*descent = (float) i_descent * scale;
*lineGap = (float) i_lineGap * scale;
}
STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer)
{
float ipw = 1.0f / pw, iph = 1.0f / ph;
@ -4269,7 +4432,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y;
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;
if (x_inter < x)
if (x_inter < x)
winding += (y0 < y1) ? 1 : -1;
}
}
@ -4295,7 +4458,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
y1 = (int)verts[i ].y;
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {
float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0;
if (x_inter < x)
if (x_inter < x)
winding += (y0 < y1) ? 1 : -1;
}
} else {
@ -4307,7 +4470,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
if (hits[1][0] < 0)
winding += (hits[1][1] < 0 ? -1 : 1);
}
}
}
}
}
return winding;
@ -4360,12 +4523,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
int w,h;
unsigned char *data;
// if one scale is 0, use same scale for both
if (scale_x == 0) scale_x = scale_y;
if (scale_y == 0) {
if (scale_x == 0) return NULL; // if both scales are 0, return NULL
scale_y = scale_x;
}
if (scale == 0) return NULL;
stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1);
@ -4388,7 +4546,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
// invert for y-downwards bitmaps
scale_y = -scale_y;
{
int x,y,i,j;
float *precompute;
@ -4537,7 +4695,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc
STBTT_free(verts, info->userdata);
}
return data;
}
}
STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff)
{
@ -4555,7 +4713,7 @@ STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata)
//
// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string
static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2)
static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2)
{
stbtt_int32 i=0;
@ -4594,7 +4752,7 @@ static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, s
return i;
}
static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2)
static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2)
{
return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2);
}
@ -4723,7 +4881,7 @@ STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset,
STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index)
{
return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index);
return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index);
}
STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data)
@ -4816,38 +4974,38 @@ This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.
------------------------------------------------------------------------------
*/

View File

@ -20,15 +20,13 @@ static double get_scale(void) {
SDL_GetDisplayDPI(0, NULL, &dpi, NULL);
#if _WIN32
return dpi / 96.0;
#elif __APPLE__
return dpi / 72.0;
#else
return 1.0;
#endif
}
static void get_exe_dir(char *buf, int sz) {
static void get_exe_filename(char *buf, int sz) {
#if _WIN32
int len = GetModuleFileName(NULL, buf, sz - 1);
buf[len] = '\0';
@ -40,19 +38,31 @@ static void get_exe_dir(char *buf, int sz) {
#elif __APPLE__
unsigned size = sz;
_NSGetExecutablePath(buf, &size);
#elif __amigaos4__
// TODO: Temporary. Needs to be done properly
strcpy(buf, "Applications:Programming/workspace/MyProjects/lite/lite");
#else
strcpy(buf, ".")
strcpy(buf, "./lite");
#endif
for (int i = strlen(buf) - 1; i > 0; i--) {
if (buf[i] == '/' || buf[i] == '\\') {
buf[i] = '\0';
break;
}
}
}
static void init_window_icon(void) {
#ifndef _WIN32
#include "../icon.inl"
(void) icon_rgba_len; /* unused */
SDL_Surface *surf = SDL_CreateRGBSurfaceFrom(
icon_rgba, 64, 64,
32, 64 * 4,
0x000000ff,
0x0000ff00,
0x00ff0000,
0xff000000);
SDL_SetWindowIcon(window, surf);
SDL_FreeSurface(surf);
#endif
}
int main(int argc, char **argv) {
#ifdef _WIN32
@ -65,6 +75,10 @@ int main(int argc, char **argv) {
SDL_EnableScreenSaver();
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
atexit(SDL_Quit);
#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* Available since 2.0.8 */
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#endif
#if SDL_VERSION_ATLEAST(2, 0, 5)
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
#endif
@ -73,8 +87,9 @@ int main(int argc, char **argv) {
SDL_GetCurrentDisplayMode(0, &dm);
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_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
init_window_icon();
ren_init(window);
@ -90,7 +105,7 @@ int main(int argc, char **argv) {
}
lua_setglobal(L, "ARGS");
lua_pushstring(L, "1.02");
lua_pushstring(L, "1.11");
lua_setglobal(L, "VERSION");
lua_pushstring(L, SDL_GetPlatform());
@ -99,10 +114,10 @@ int main(int argc, char **argv) {
lua_pushnumber(L, get_scale());
lua_setglobal(L, "SCALE");
char exedir[2048];
get_exe_dir(exedir, sizeof(exedir));
lua_pushstring(L, exedir);
lua_setglobal(L, "EXEDIR");
char exename[2048];
get_exe_filename(exename, sizeof(exename));
lua_pushstring(L, exename);
lua_setglobal(L, "EXEFILE");
(void) luaL_dostring(L,
@ -110,6 +125,7 @@ int main(int argc, char **argv) {
"xpcall(function()\n"
" SCALE = tonumber(os.getenv(\"LITE_SCALE\")) or SCALE\n"
" PATHSEP = package.config:sub(1, 1)\n"
" EXEDIR = EXEFILE:match(\"^(.+)[/\\\\].*$\")\n"
" package.path = EXEDIR .. '/data/?.lua;' .. package.path\n"
" package.path = EXEDIR .. '/data/?/init.lua;' .. package.path\n"
" core = require('core')\n"

View File

@ -1,9 +1,9 @@
#include <stdio.h>
#include "rencache.h"
/* a cache over the software renderer -- all drawing operations are stored
** as commands when issued. At the end of the frame we write the commands to
** a spatial hash, take the cells that have changed since the previous frame,
/* a cache over the software renderer -- all drawing operations are stored as
** commands when issued. At the end of the frame we write the commands to a grid
** of hash values, take the cells that have changed since the previous frame,
** merge them into dirty rectangles and redraw only those regions */
#define CELLS_X 80
@ -18,6 +18,7 @@ typedef struct {
RenRect rect;
RenColor color;
RenFont *font;
int tab_width;
char text[0];
} Command;
@ -78,13 +79,13 @@ static RenRect merge_rects(RenRect a, RenRect b) {
static Command* push_command(int type, int size) {
Command *cmd = (Command*) (command_buf + command_buf_idx);
memset(cmd, 0, sizeof(Command));
int n = command_buf_idx + size;
if (n > COMMAND_BUF_SIZE) {
fprintf(stderr, "Fatal error in " __FILE__ ": exhausted command buffer\n");
exit(EXIT_FAILURE);
fprintf(stderr, "Warning: (" __FILE__ "): exhausted command buffer\n");
return NULL;
}
command_buf_idx = n;
memset(cmd, 0, sizeof(Command));
cmd->type = type;
cmd->size = size;
return cmd;
@ -108,34 +109,51 @@ void rencache_show_debug(bool enable) {
void rencache_free_font(RenFont *font) {
Command *cmd = push_command(FREE_FONT, sizeof(Command));
cmd->font = font;
if (cmd) { cmd->font = font; }
}
void rencache_set_clip_rect(RenRect rect) {
Command *cmd = push_command(SET_CLIP, sizeof(Command));
cmd->rect = intersect_rects(rect, screen_rect);
if (cmd) { cmd->rect = intersect_rects(rect, screen_rect); }
}
void rencache_draw_rect(RenRect rect, RenColor color) {
if (!rects_overlap(screen_rect, rect)) { return; }
Command *cmd = push_command(DRAW_RECT, sizeof(Command));
cmd->rect = rect;
cmd->color = color;
if (cmd) {
cmd->rect = rect;
cmd->color = color;
}
}
int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor color) {
int sz = strlen(text) + 1;
Command *cmd = push_command(DRAW_TEXT, sizeof(Command) + sz);
memcpy(cmd->text, text, sz);
cmd->color = color;
cmd->font = font;
cmd->rect.x = x;
cmd->rect.y = y;
cmd->rect.width = ren_get_font_width(font, text);
cmd->rect.height = ren_get_font_height(font);
return x + cmd->rect.width;
RenRect rect;
rect.x = x;
rect.y = y;
rect.width = ren_get_font_width(font, text);
rect.height = ren_get_font_height(font);
if (rects_overlap(screen_rect, rect)) {
int sz = strlen(text) + 1;
Command *cmd = push_command(DRAW_TEXT, sizeof(Command) + sz);
if (cmd) {
memcpy(cmd->text, text, sz);
cmd->color = color;
cmd->font = font;
cmd->rect = rect;
cmd->tab_width = ren_get_font_tab_width(font);
}
}
return x + rect.width;
}
void rencache_invalidate(void) {
memset(cells_prev, 0xff, sizeof(cells_buf1));
}
@ -146,7 +164,7 @@ void rencache_begin_frame(void) {
if (screen_rect.width != w || h != screen_rect.height) {
screen_rect.width = w;
screen_rect.height = h;
memset(cells_prev, 0xff, sizeof(cells_buf1));
rencache_invalidate();
}
}
@ -238,6 +256,7 @@ void rencache_end_frame(void) {
ren_draw_rect(cmd->rect, cmd->color);
break;
case DRAW_TEXT:
ren_set_font_tab_width(cmd->font, cmd->tab_width);
ren_draw_text(cmd->font, cmd->text, cmd->rect.x, cmd->rect.y, cmd->color);
break;
}

View File

@ -9,6 +9,7 @@ void rencache_free_font(RenFont *font);
void rencache_set_clip_rect(RenRect rect);
void rencache_draw_rect(RenRect rect, RenColor color);
int rencache_draw_text(RenFont *font, const char *text, int x, int y, RenColor color);
void rencache_invalidate(void);
void rencache_begin_frame(void);
void rencache_end_frame(void);

View File

@ -1,8 +1,8 @@
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <math.h>
#include "lib/stb/stb_truetype.h"
#include "xalloc.h"
#include "renderer.h"
#define MAX_GLYPHSET 256
@ -30,6 +30,15 @@ static SDL_Window *window;
static struct { int left, top, right, bottom; } clip;
static void* check_alloc(void *ptr) {
if (!ptr) {
fprintf(stderr, "Fatal error: memory allocation failed\n");
exit(EXIT_FAILURE);
}
return ptr;
}
static const char* utf8_to_codepoint(const char *p, unsigned *dst) {
unsigned res, n;
switch (*p & 0xf0) {
@ -57,6 +66,11 @@ void ren_init(SDL_Window *win) {
void ren_update_rects(RenRect *rects, int count) {
SDL_UpdateWindowSurfaceRects(window, (SDL_Rect*) rects, count);
static bool initial_frame = true;
if (initial_frame) {
SDL_ShowWindow(window);
initial_frame = false;
}
}
@ -77,7 +91,8 @@ void ren_get_size(int *x, int *y) {
RenImage* ren_new_image(int width, int height) {
assert(width > 0 && height > 0);
RenImage *image = xmalloc(sizeof(RenImage) + width * height * sizeof(RenColor));
RenImage *image = malloc(sizeof(RenImage) + width * height * sizeof(RenColor));
check_alloc(image);
image->pixels = (void*) (image + 1);
image->width = width;
image->height = height;
@ -86,12 +101,12 @@ RenImage* ren_new_image(int width, int height) {
void ren_free_image(RenImage *image) {
xfree(image);
free(image);
}
static GlyphSet* load_glyphset(RenFont *font, int idx) {
GlyphSet *set = xcalloc(1, sizeof(GlyphSet));
GlyphSet *set = check_alloc(calloc(1, sizeof(GlyphSet)));
/* init image */
int width = 128;
@ -149,7 +164,7 @@ RenFont* ren_load_font(const char *filename, float size) {
FILE *fp = NULL;
/* init font */
font = xcalloc(1, sizeof(RenFont));
font = check_alloc(calloc(1, sizeof(RenFont)));
font->size = size;
/* load font into buffer */
@ -158,7 +173,7 @@ RenFont* ren_load_font(const char *filename, float size) {
/* get size */
fseek(fp, 0, SEEK_END); int buf_size = ftell(fp); fseek(fp, 0, SEEK_SET);
/* load */
font->data = xmalloc(buf_size);
font->data = check_alloc(malloc(buf_size));
int _ = fread(font->data, 1, buf_size, fp); (void) _;
fclose(fp);
fp = NULL;
@ -182,8 +197,8 @@ RenFont* ren_load_font(const char *filename, float size) {
fail:
if (fp) { fclose(fp); }
if (font) { xfree(font->data); }
xfree(font);
if (font) { free(font->data); }
free(font);
return NULL;
}
@ -193,11 +208,11 @@ void ren_free_font(RenFont *font) {
GlyphSet *set = font->sets[i];
if (set) {
ren_free_image(set->image);
xfree(set);
free(set);
}
}
xfree(font->data);
xfree(font);
free(font->data);
free(font);
}
@ -207,6 +222,12 @@ void ren_set_font_tab_width(RenFont *font, int n) {
}
int ren_get_font_tab_width(RenFont *font) {
GlyphSet *set = get_glyphset(font, '\t');
return set->glyphs['\t'].xadvance;
}
int ren_get_font_width(RenFont *font, const char *text) {
int x = 0;
const char *p = text;
@ -249,7 +270,7 @@ static inline RenColor blend_pixel2(RenColor dst, RenColor src, RenColor color)
for (int j = y1; j < y2; j++) { \
for (int i = x1; i < x2; i++) { \
*d = expr; \
d++; \
d++; \
} \
d += dr; \
}
@ -263,17 +284,22 @@ void ren_draw_rect(RenRect rect, RenColor color) {
int y2 = rect.y + rect.height;
x2 = x2 > clip.right ? clip.right : x2;
y2 = y2 > clip.bottom ? clip.bottom : y2;
printf("DBG: rect\tx1: %d\ty1: %d\tx2:%d\ty2:%d\n", x1, y1, x2, y2);
SDL_Surface *surf = SDL_GetWindowSurface(window);
RenColor *d = (RenColor*) surf->pixels;
d += x1 + y1 * surf->w;
int dr = surf->w - (x2 - x1);
printf("DBG: surf\tr: %d\tg: %d\tb:%d\ta:%d\tw: %d\n", d->r, d->g, d->b, d->a, surf->w);
d += x1 + y1 * (surf->pitch / 4);
//printf("DBG: surf\tr: %d\tg: %d\tb:%d\ta:%d\n", d->r, d->g, d->b, d->a);
int dr = (surf->pitch / 4) - (x2 - x1);
printf("DBG: r: %d\tg: %d\tb:%d\ta:%d\n", color.r, color.g, color.b, color.a);
if (color.a == 0xff) {
rect_draw_loop(color);
//rect_draw_loop(color);
SDL_Rect rect = { x1, y1, x2 - x1, y2 - y1 };
SDL_FillRect(surf, &rect, SDL_MapRGBA(surf->format, color.r, color.g, color.b, color.a));
} else {
rect_draw_loop(blend_pixel(*d, color));
rect_draw_loop(blend_pixel(*d, color));
}
printf("======================\n");
}

View File

@ -22,6 +22,7 @@ void ren_free_image(RenImage *image);
RenFont* ren_load_font(const char *filename, float size);
void ren_free_font(RenFont *font);
void ren_set_font_tab_width(RenFont *font, int n);
int ren_get_font_tab_width(RenFont *font);
int ren_get_font_width(RenFont *font, const char *text);
int ren_get_font_height(RenFont *font);

View File

@ -1,40 +0,0 @@
#include <stdio.h>
#include "xalloc.h"
static void panic(void) {
fprintf(stderr, "Fatal error: out of memory\n");
exit(1);
}
void* xmalloc(size_t size) {
void *ptr = malloc(size);
if (!ptr) {
panic();
}
return ptr;
}
void xfree(void *ptr) {
free(ptr);
}
void* xcalloc(size_t nmemb, size_t size) {
void *ptr = calloc(nmemb, size);
if (!ptr) {
panic();
}
return ptr;
}
void* xrealloc(void *ptr, size_t size) {
ptr = realloc(ptr, size);
if (!ptr) {
panic();
}
return ptr;
}

View File

@ -1,11 +0,0 @@
#ifndef XALLOC_H
#define XALLOC_H
#include <stdlib.h>
void* xmalloc(size_t size);
void xfree(void *ptr);
void* xcalloc(size_t nmemb, size_t size);
void* xrealloc(void *ptr, size_t size);
#endif