Compare commits
162 Commits
Author | SHA1 | Date |
---|---|---|
George Sokianos | 1abca0d4c7 | |
George Sokianos | 3a5cadc116 | |
rxi | 38bd9b3326 | |
takase1121 | efa73e1ed1 | |
rxi | 806f0e39e3 | |
rxi | 79c4f9fcae | |
rxi | b2ddc140d6 | |
rxi | de5cb4fa52 | |
rxi | 878c94a334 | |
rxi | 2caa7f182d | |
rxi | 91c43dc01e | |
rxi | 99831bbc29 | |
rxi | 87532a4b3a | |
rxi | 094cf0cc2c | |
rxi | 53d555b362 | |
rxi | 11df722162 | |
rxi | ae48049695 | |
rxi | 6ec8fc5616 | |
rxi | 1db1f0bceb | |
rxi | 1a82fd2b92 | |
rxi | 7517d0ef55 | |
rxi | 95b70b1b16 | |
rxi | ba6c14846b | |
rxi | db471c0554 | |
rxi | 6a7e214d1c | |
rxi | 877d940c0e | |
rxi | bd0644a5bb | |
rxi | 3569abcb53 | |
rxi | dc766a644f | |
Daniele Laudani | 95ee03fb37 | |
Daniele Laudani | 7aa462e43d | |
rxi | 18de4552e2 | |
rxi | 4b167e86c6 | |
Victor Gridnevsky | a6f52197d0 | |
rxi | f00d5d55df | |
rxi | 508b6fb73a | |
rxi | db8c5ea2aa | |
rxi | 7fbefe40d5 | |
rxi | cc58fcc35b | |
rxi | b96609b7b8 | |
rxi | 1b2fda2825 | |
rxi | 9c652086e8 | |
rxi | 74755f5b4a | |
rxi | e7cf551e22 | |
rxi | 064b6d0b95 | |
rxi | 257b9ab753 | |
rxi | 61a2a2c4e5 | |
rxi | c2d27ab3f7 | |
rxi | 82e33dd2de | |
rxi | 946c125fd4 | |
rxi | 61092fbb99 | |
rxi | 28b1844a8b | |
rxi | e45b3e2bc0 | |
rxi | e6a2770e2e | |
rxi | 71fb50ece0 | |
rxi | e7320c2291 | |
rxi | 35b642d434 | |
rxi | 35ce3d32a9 | |
Bruce Mitchener | c9f798a07b | |
rxi | 6b39fb6dfb | |
rxi | 7aabfebfa0 | |
rxi | 08ce7e2563 | |
rxi | bc4bf3d384 | |
rxi | 8ec717f240 | |
rxi | 4ae0d477c0 | |
rxi | c1f731e5a1 | |
rxi | adad2a65be | |
rxi | adecaba292 | |
rxi | 4644154e5b | |
rxi | 1abb979490 | |
rxi | 82fdc63c6a | |
rxi | 7e7602c53c | |
rxi | 59f5692f2e | |
rxi | 0dd4811465 | |
rxi | bc3147e1d0 | |
rxi | 15129b49a6 | |
demotulatingswan | 92b3b5ba86 | |
rxi | 5102088aca | |
rxi | b8d2805502 | |
rxi | ef53453246 | |
rxi | 6525269386 | |
rxi | bcd1b3a081 | |
rxi | 9bf0ed2419 | |
rxi | 4c2c03ed4d | |
rxi | 2b32edf7f0 | |
rxi | e4ae088bb5 | |
rxi | 1f55fec94b | |
rxi | ed86f7d04e | |
rxi | 7f6a2710ef | |
rxi | 23cf193026 | |
rxi | ff2c7bf5e5 | |
rxi | af36658e68 | |
rxi | 2f659d5180 | |
extrowerk | a6013ff181 | |
rxi | c215eff6d8 | |
rxi | b67b680975 | |
rxi | f7b54db1b4 | |
rxi | 3067443432 | |
Willie Lawrence | a17fe46c05 | |
rxi | c4be26ccaa | |
rxi | 22f563e712 | |
rxi | ae20c40554 | |
Daniel M | 2ed29cae11 | |
Daniel M | 3974971ff9 | |
rxi | 543234c42e | |
rxi | 8671b02bdc | |
rxi | 143f8867a1 | |
rxi | 3d49b6d200 | |
rxi | 70f62f3c8a | |
rxi | 7479c1380d | |
rxi | b08f870f47 | |
rxi | 78e729f580 | |
rxi | ee19d4644d | |
rxi | ffdaec47e8 | |
rxi | dfcbc48aad | |
rxi | 18b7d70a91 | |
rxi | a651d48e84 | |
rxi | 22171fa802 | |
rxi | 31820b36ef | |
rxi | a754c60127 | |
rxi | 2642f7443f | |
rxi | b2756d8a49 | |
rxi | 1d2a0aada5 | |
rxi | bf8565d2a1 | |
rxi | 95bdb07d49 | |
rxi | de94c8a13c | |
rxi | 774d95d800 | |
rxi | 762c1e2b69 | |
rxi | f5025efbb8 | |
rxi | ae42176953 | |
rxi | 7cdf7dc44f | |
rxi | 5acc391288 | |
rxi | 596b40c741 | |
rxi | 3057786ce2 | |
rxi | e551052e91 | |
rxi | 201c8ffe9f | |
rxi | 73996e3dc9 | |
rxi | 5361bfaf9c | |
rxi | 271e5434d0 | |
rxi | 7610e1064f | |
rxi | 15cfbfbc46 | |
rxi | 9bd6efddd7 | |
rxi | d5ffee51ff | |
rxi | 844dced7cd | |
rxi | d859ce5fcd | |
rxi | 69e6550eba | |
rxi | 05ca43e637 | |
rxi | 468905830f | |
rxi | 26d6e558f1 | |
rxi | 0967740d88 | |
rxi | 5155ce0527 | |
rxi | 9fc185af2f | |
rxi | 28cdd3cabe | |
rxi | a9f3079c90 | |
rxi | 4ca35fe056 | |
rxi | 885ed5f860 | |
rxi | ab8510291e | |
rxi | 044fdb3655 | |
rxi | 4d39dcaded | |
rxi | 2090379892 | |
rxi | 439537d63e | |
rxi | 16c1039666 |
|
@ -0,0 +1,3 @@
|
|||
winlib/* linguist-vendored
|
||||
src/lib/* linguist-vendored
|
||||
icon.inl linguist-vendored
|
|
@ -0,0 +1,3 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: rxi
|
|
@ -0,0 +1,5 @@
|
|||
src/*.o
|
||||
src/*/*.o
|
||||
src/*/*/*.o
|
||||
lite
|
||||
*.txt
|
|
@ -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
|
||||
|
33
README.md
33
README.md
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
306
build.py
|
@ -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)
|
|
@ -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"
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
})
|
|
@ -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",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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 = { },
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = "%.py$",
|
||||
files = { "%.py$", "%.pyw$" },
|
||||
headers = "^#!.*[ /]python",
|
||||
comment = "#",
|
||||
patterns = {
|
||||
{ pattern = { "#", "\n" }, type = "comment" },
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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
BIN
icon.ico
Binary file not shown.
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" ?>
|
||||
<CodeBench_Project name="lite" path="SDH3:Programming/workspace/MyProjects/lite/lite.cbp" created="1387049720" lastmodified="1387152257">
|
||||
<plugin name="PROGDIR:Plugins/AmigaOS4SDK.CCPlugin" flags="0"/>
|
||||
<target/>
|
||||
<homedir name="SDH3:Programming/workspace/MyProjects/lite"/>
|
||||
<includedir name="Applications:Programming/workspace/OtherProjects/lite/src"/>
|
||||
<compiler name="gcc:bin/gcc" switches="-D__USE_INLINE__ -Wall -Werror -Wwrite-strings" stack="131072"/>
|
||||
<linker switches="-lauto"/>
|
||||
<debugger name="SDK:c/gdb"/>
|
||||
<builder name="SDK:c/make -f"/>
|
||||
<environment/>
|
||||
<headers>
|
||||
<file name="Applications:Programming/workspace/OtherProjects/lite/src/api/api.h" open="0"/>
|
||||
<file name="Applications:Programming/workspace/OtherProjects/lite/src/rencache.h" open="0"/>
|
||||
<file name="Applications:Programming/workspace/OtherProjects/lite/src/lib/stb/stb_truetype.h" open="0"/>
|
||||
<file name="Applications:Programming/workspace/OtherProjects/lite/src/renderer.h" open="0"/>
|
||||
</headers>
|
||||
<sources>
|
||||
<file name="src/api/api.c" open="0"/>
|
||||
<file name="src/api/renderer.c" open="0"/>
|
||||
<file name="src/api/renderer_font.c" open="0"/>
|
||||
<file name="src/api/system.c" open="0"/>
|
||||
<file name="src/main.c" open="1" current="1" top="75" left="0" line="97" row="16"/>
|
||||
<file name="src/rencache.c" open="0"/>
|
||||
<file name="src/renderer.c" open="0"/>
|
||||
<file name="src/lib/stb/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/gcc" switches="-llua -lSDL2 -lpthread -lauto" flags="0x00000002">
|
||||
<file name="src/lib/stb/stb_truetype.c"/>
|
||||
<file name="src/api/renderer.c"/>
|
||||
<file name="src/api/renderer_font.c"/>
|
||||
<file name="src/api/system.c"/>
|
||||
<file name="src/main.c"/>
|
||||
<file name="src/api/api.c"/>
|
||||
<file name="src/rencache.c"/>
|
||||
<file name="src/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>
|
Binary file not shown.
|
@ -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) {
|
||||
|
|
|
@ -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 }
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
|
|
52
src/main.c
52
src/main.c
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
40
src/xalloc.c
40
src/xalloc.c
|
@ -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;
|
||||
}
|
11
src/xalloc.h
11
src/xalloc.h
|
@ -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
|
Loading…
Reference in New Issue