sync fwk
parent
7c84c3b7a4
commit
97973d344f
51
MAKE.bat
51
MAKE.bat
|
@ -410,6 +410,51 @@ if "%1"=="back" (
|
|||
exit /b
|
||||
)
|
||||
|
||||
if "%1"=="fwk_mir" (
|
||||
rd/q/s _fwk
|
||||
rd/q/s ..\_fwk
|
||||
mkdir ..\_fwk
|
||||
setlocal enabledelayedexpansion
|
||||
xcopy /y/E "*" "..\_fwk"
|
||||
move "..\_fwk" _fwk
|
||||
for %%f in ("engine\split\v4k*") do (
|
||||
set "filename=%%~nf"
|
||||
set "newname=fwk!filename:v4k=!%%~xf"
|
||||
echo Copying and renaming "%%f" to "_fwk\engine\split\!newname!"
|
||||
copy "%%f" "_fwk\engine\split\!newname!"
|
||||
)
|
||||
for %%f in (_fwk\engine\split\*) do (
|
||||
set "filename=%%~nxf"
|
||||
if /i not "!filename:~0,4!"=="3rd_" (
|
||||
echo Processing: %%f
|
||||
tools\fwkren.exe %%f from
|
||||
) else (
|
||||
echo Skipping %%f
|
||||
)
|
||||
)
|
||||
for %%f in (_fwk\demos\*.c) do (
|
||||
set "filename=%%~nxf"
|
||||
echo Processing: %%f
|
||||
tools\fwkren.exe %%f from
|
||||
)
|
||||
for %%f in (_fwk\tools\*) do (
|
||||
set "filename=%%~nxf"
|
||||
echo Processing: %%f
|
||||
tools\fwkren.exe %%f from
|
||||
)
|
||||
for %%f in (_fwk\tools\editor\*.c) do (
|
||||
set "filename=%%~nxf"
|
||||
echo Processing: %%f
|
||||
tools\fwkren.exe %%f from
|
||||
)
|
||||
|
||||
tools\fwkren.exe _fwk\tools\cook.ini from
|
||||
|
||||
echo All done.
|
||||
endlocal
|
||||
exit /b
|
||||
)
|
||||
|
||||
rem copy demos to root folder. local changes are preserved
|
||||
rem echo n | copy /-y demos\*.c 1> nul 2> nul
|
||||
|
||||
|
@ -757,10 +802,10 @@ if "!v4k!"=="yes" (
|
|||
|
||||
rem editor
|
||||
if "!editor!"=="yes" (
|
||||
set edit=-DCOOK_ON_DEMAND -DUI_LESSER_SPACING -DUI_ICONS_SMALL
|
||||
if "!vis!"=="yes" echo !cc! !o! editor.exe tools\editor\editor.c !edit! !import! !args!
|
||||
!echo! editor && !cc! !o! editor.exe tools\editor\editor.c !edit! !import! !args! || set rc=1
|
||||
set edit=-DCOOK_ON_DEMAND
|
||||
!echo! editor3 && !cc! !o! editor3.exe tools\editor\editor3.c !edit! -Iengine/joint !args! || set rc=1
|
||||
set edit=-DUI_LESSER_SPACING -DUI_ICONS_SMALL !edit!
|
||||
!echo! editor && !cc! !o! editor.exe tools\editor\editor.c !edit! !import! !args! || set rc=1
|
||||
|
||||
rem if "!cc!"=="cl" (
|
||||
rem set plug_export=/LD
|
||||
|
|
176
bind/v4k.lua
176
bind/v4k.lua
|
@ -1130,6 +1130,7 @@ ffi.cdef([[
|
|||
//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca);
|
||||
//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca);
|
||||
//lcpp INF [0000] vec3: macro name but used as C declaration in: int gizmo(vec3 *pos, vec3 *rot, vec3 *sca);
|
||||
//lcpp INF [0000] map: macro name but used as C declaration in:typedef struct { map base; struct { pair p; char* key; char* val; } tmp, *ptr; char** tmpval; int (*typed_cmp)(char*, char*); uint64_t (*typed_hash)(char*); } * ini_t;
|
||||
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 font_xy();
|
||||
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 font_xy();
|
||||
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 font_xy();
|
||||
|
@ -1142,7 +1143,6 @@ ffi.cdef([[
|
|||
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 font_highlight(const char *text, const void *colors);
|
||||
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 font_highlight(const char *text, const void *colors);
|
||||
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 font_highlight(const char *text, const void *colors);
|
||||
//lcpp INF [0000] map: macro name but used as C declaration in:typedef struct { map base; struct { pair p; char* key; char* val; } tmp, *ptr; char** tmpval; int (*typed_cmp)(char*, char*); uint64_t (*typed_hash)(char*); } * ini_t;
|
||||
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 input2( int vk );
|
||||
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 input2( int vk );
|
||||
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 input2( int vk );
|
||||
|
@ -1508,6 +1508,9 @@ ffi.cdef([[
|
|||
//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC void light_dir(light_t* l, vec3 dir);
|
||||
//lcpp INF [0000] vec3: macro name but used as C declaration in: void light_dir(light_t* l, vec3 dir);
|
||||
//lcpp INF [0000] vec2i: macro name but used as C declaration in:vec2i* entries;
|
||||
//lcpp INF [0000] test: macro name but used as C declaration in:API int (test)(const char *file, int line, const char *expr, bool result);
|
||||
//lcpp INF [0000] test: macro name but used as C declaration in:STATIC int (test)(const char *file, int line, const char *expr, bool result);
|
||||
//lcpp INF [0000] test: macro name but used as C declaration in: int (test)(const char *file, int line, const char *expr, bool result);
|
||||
//lcpp INF [0000] vec3i: macro name but used as C declaration in:typedef vec3i guid;
|
||||
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 v;
|
||||
//lcpp INF [0000] vec3: macro name but used as C declaration in:vec3 result;
|
||||
|
@ -1522,9 +1525,6 @@ ffi.cdef([[
|
|||
//lcpp INF [0000] vec3: macro name but used as C declaration in:API vec3 curve_eval(curve_t *c, float dt, unsigned *color);
|
||||
//lcpp INF [0000] vec3: macro name but used as C declaration in:STATIC vec3 curve_eval(curve_t *c, float dt, unsigned *color);
|
||||
//lcpp INF [0000] vec3: macro name but used as C declaration in: vec3 curve_eval(curve_t *c, float dt, unsigned *color);
|
||||
//lcpp INF [0000] test: macro name but used as C declaration in:API int (test)(const char *file, int line, const char *expr, bool result);
|
||||
//lcpp INF [0000] test: macro name but used as C declaration in:STATIC int (test)(const char *file, int line, const char *expr, bool result);
|
||||
//lcpp INF [0000] test: macro name but used as C declaration in: int (test)(const char *file, int line, const char *expr, bool result);
|
||||
//lcpp INF [0000] vec2: macro name but used as C declaration in:API vec2 ui_get_dims();
|
||||
//lcpp INF [0000] vec2: macro name but used as C declaration in:STATIC vec2 ui_get_dims();
|
||||
//lcpp INF [0000] vec2: macro name but used as C declaration in: vec2 ui_get_dims();
|
||||
|
@ -2117,6 +2117,11 @@ typedef union json_t { char* s; double f; int64_t i; uintptr_t p; union json_t*
|
|||
void script_bind_function(const char *c_name, void *c_function);
|
||||
void script_call(const char *lua_function);
|
||||
bool script_tests();
|
||||
enum {
|
||||
SCRIPT_LUA = 1,
|
||||
SCRIPT_DEBUGGER = 2,
|
||||
};
|
||||
void *script_init_env(unsigned flags);
|
||||
vec3 editor_pick(float mouse_x, float mouse_y);
|
||||
char* editor_path(const char *path);
|
||||
float* engine_getf(const char *key);
|
||||
|
@ -2139,38 +2144,6 @@ typedef union json_t { char* s; double f; int64_t i; uintptr_t p; union json_t*
|
|||
char* kit_translate2( const char *id, const char *langcode_iso639_1 );
|
||||
void kit_locale( const char *langcode_iso639_1 );
|
||||
char* kit_translate( const char *id );
|
||||
enum FONT_FLAGS {
|
||||
FONT_512 =0,
|
||||
FONT_1024 =1,
|
||||
FONT_2048 =2,
|
||||
FONT_4096 =4,
|
||||
FONT_NO_OVERSAMPLE =0,
|
||||
FONT_OVERSAMPLE_X =8,
|
||||
FONT_OVERSAMPLE_Y =16,
|
||||
FONT_ASCII =2048,
|
||||
FONT_AR =4096,
|
||||
FONT_ZH =8192,
|
||||
FONT_EL =16384,
|
||||
FONT_EM =32768,
|
||||
FONT_EU =65536,
|
||||
FONT_HE =131072,
|
||||
FONT_JP =262144,
|
||||
FONT_KR =524288,
|
||||
FONT_RU =1048576,
|
||||
FONT_TH =2097152,
|
||||
FONT_VI =4194304,
|
||||
FONT_CJK = FONT_ZH|FONT_JP|FONT_KR,
|
||||
};
|
||||
void font_face(const char *face_tag, const char *filename_ttf, float font_size, unsigned flags);
|
||||
void font_face_from_mem(const char *tag, const void *ttf_buffer, unsigned ttf_len, float font_size, unsigned flags);
|
||||
void font_scales(const char *face_tag, float h1, float h2, float h3, float h4, float h5, float h6);
|
||||
void font_color(const char *color_tag, uint32_t color);
|
||||
vec2 font_xy();
|
||||
void font_goto(float x, float y);
|
||||
vec2 font_print(const char *text);
|
||||
vec2 font_rect(const char *text);
|
||||
void* font_colorize(const char *text, const char *comma_types, const char *comma_keywords);
|
||||
vec2 font_highlight(const char *text, const void *colors);
|
||||
char** file_list( const char *pathmasks );
|
||||
bool file_write( const char *file, const void *ptr, int len );
|
||||
bool file_append( const char *file, const void *ptr, int len );
|
||||
|
@ -2218,6 +2191,38 @@ typedef struct { map base; struct { pair p; char* key; char* val; } tmp, *ptr; c
|
|||
ini_t ini_from_mem(const char *data);
|
||||
void ini_destroy(ini_t);
|
||||
bool ini_write(const char *filename, const char *section, const char *key, const char *value);
|
||||
enum FONT_FLAGS {
|
||||
FONT_512 =0,
|
||||
FONT_1024 =1,
|
||||
FONT_2048 =2,
|
||||
FONT_4096 =4,
|
||||
FONT_NO_OVERSAMPLE =0,
|
||||
FONT_OVERSAMPLE_X =8,
|
||||
FONT_OVERSAMPLE_Y =16,
|
||||
FONT_ASCII =2048,
|
||||
FONT_AR =4096,
|
||||
FONT_ZH =8192,
|
||||
FONT_EL =16384,
|
||||
FONT_EM =32768,
|
||||
FONT_EU =65536,
|
||||
FONT_HE =131072,
|
||||
FONT_JP =262144,
|
||||
FONT_KR =524288,
|
||||
FONT_RU =1048576,
|
||||
FONT_TH =2097152,
|
||||
FONT_VI =4194304,
|
||||
FONT_CJK = FONT_ZH|FONT_JP|FONT_KR,
|
||||
};
|
||||
void font_face(const char *face_tag, const char *filename_ttf, float font_size, unsigned flags);
|
||||
void font_face_from_mem(const char *tag, const void *ttf_buffer, unsigned ttf_len, float font_size, unsigned flags);
|
||||
void font_scales(const char *face_tag, float h1, float h2, float h3, float h4, float h5, float h6);
|
||||
void font_color(const char *color_tag, uint32_t color);
|
||||
vec2 font_xy();
|
||||
void font_goto(float x, float y);
|
||||
vec2 font_print(const char *text);
|
||||
vec2 font_rect(const char *text);
|
||||
void* font_colorize(const char *text, const char *comma_types, const char *comma_keywords);
|
||||
vec2 font_highlight(const char *text, const void *colors);
|
||||
int input_use( int controller_id );
|
||||
float input( int vk );
|
||||
vec2 input2( int vk );
|
||||
|
@ -2605,6 +2610,7 @@ TEXTURE_RGBA = IMAGE_RGBA,
|
|||
TEXTURE_FLIP = IMAGE_FLIP,
|
||||
TEXTURE_SRGB = 1 << 24,
|
||||
TEXTURE_BGR = 1 << 25,
|
||||
TEXTURE_BGRA = TEXTURE_BGR,
|
||||
TEXTURE_ARRAY = 1 << 26,
|
||||
};
|
||||
typedef struct texture_t {
|
||||
|
@ -3128,6 +3134,51 @@ vec2i* entries;
|
|||
} quarks_db;
|
||||
unsigned quark_intern( quarks_db*, const char *string );
|
||||
const char *quark_string( quarks_db*, unsigned key );
|
||||
void* thread( int (*thread_func)(void* user_data), void* user_data );
|
||||
void thread_destroy( void *thd );
|
||||
int argc();
|
||||
char* argv(int);
|
||||
int flag(const char *commalist);
|
||||
const char* option(const char *commalist, const char *defaults);
|
||||
int optioni(const char *commalist, int defaults);
|
||||
float optionf(const char *commalist, float defaults);
|
||||
void tty_attach();
|
||||
void tty_detach();
|
||||
void tty_color(unsigned color);
|
||||
void tty_reset();
|
||||
const char* app_exec(const char *command);
|
||||
int app_spawn(const char *command);
|
||||
int app_cores();
|
||||
int app_battery();
|
||||
const char* app_name();
|
||||
const char* app_path();
|
||||
const char* app_cache();
|
||||
const char* app_temp();
|
||||
const char* app_cmdline();
|
||||
void app_beep();
|
||||
void app_hang();
|
||||
void app_crash();
|
||||
void app_singleton(const char *guid);
|
||||
bool app_open(const char *folder_file_or_url);
|
||||
const char* app_loadfile();
|
||||
const char* app_savefile();
|
||||
char* callstack( int traces );
|
||||
int callstackf( FILE *fp, int traces );
|
||||
void die(const char *message);
|
||||
void alert(const char *message);
|
||||
void hexdump( const void *ptr, unsigned len );
|
||||
void hexdumpf( FILE *fp, const void *ptr, unsigned len, int width );
|
||||
void breakpoint();
|
||||
bool has_debugger();
|
||||
void trap_install(void);
|
||||
const char *trap_name(int signal);
|
||||
void trap_on_ignore(int signal);
|
||||
void trap_on_quit(int signal);
|
||||
void trap_on_abort(int signal);
|
||||
void trap_on_debug(int signal);
|
||||
int (PANIC)(const char *error, const char *file, int line);
|
||||
int (PRINTF)(const char *text, const char *stack, const char *file, int line, const char *function);
|
||||
int (test)(const char *file, int line, const char *expr, bool result);
|
||||
uint64_t date();
|
||||
uint64_t date_epoch();
|
||||
char* date_string();
|
||||
|
@ -3229,54 +3280,9 @@ int* indices;
|
|||
} curve_t;
|
||||
curve_t curve();
|
||||
void curve_add(curve_t *c, vec3 p);
|
||||
void curve_finish(curve_t *c, int num_points);
|
||||
void curve_end(curve_t *c, int num_points);
|
||||
vec3 curve_eval(curve_t *c, float dt, unsigned *color);
|
||||
void curve_destroy(curve_t *c);
|
||||
void* thread( int (*thread_func)(void* user_data), void* user_data );
|
||||
void thread_destroy( void *thd );
|
||||
int argc();
|
||||
char* argv(int);
|
||||
int flag(const char *commalist);
|
||||
const char* option(const char *commalist, const char *defaults);
|
||||
int optioni(const char *commalist, int defaults);
|
||||
float optionf(const char *commalist, float defaults);
|
||||
void tty_attach();
|
||||
void tty_detach();
|
||||
void tty_color(unsigned color);
|
||||
void tty_reset();
|
||||
const char* app_exec(const char *command);
|
||||
int app_spawn(const char *command);
|
||||
int app_cores();
|
||||
int app_battery();
|
||||
const char* app_name();
|
||||
const char* app_path();
|
||||
const char* app_cache();
|
||||
const char* app_temp();
|
||||
const char* app_cmdline();
|
||||
void app_beep();
|
||||
void app_hang();
|
||||
void app_crash();
|
||||
void app_singleton(const char *guid);
|
||||
bool app_open(const char *folder_file_or_url);
|
||||
const char* app_loadfile();
|
||||
const char* app_savefile();
|
||||
char* callstack( int traces );
|
||||
int callstackf( FILE *fp, int traces );
|
||||
void die(const char *message);
|
||||
void alert(const char *message);
|
||||
void hexdump( const void *ptr, unsigned len );
|
||||
void hexdumpf( FILE *fp, const void *ptr, unsigned len, int width );
|
||||
void breakpoint();
|
||||
bool has_debugger();
|
||||
void trap_install(void);
|
||||
const char *trap_name(int signal);
|
||||
void trap_on_ignore(int signal);
|
||||
void trap_on_quit(int signal);
|
||||
void trap_on_abort(int signal);
|
||||
void trap_on_debug(int signal);
|
||||
int (PANIC)(const char *error, const char *file, int line);
|
||||
int (PRINTF)(const char *text, const char *stack, const char *file, int line, const char *function);
|
||||
int (test)(const char *file, int line, const char *expr, bool result);
|
||||
enum PANEL_FLAGS {
|
||||
PANEL_OPEN = 1,
|
||||
};
|
||||
|
@ -3438,13 +3444,15 @@ enum CURSOR_SHAPES {
|
|||
CURSOR_NONE,
|
||||
CURSOR_HW_ARROW,
|
||||
CURSOR_HW_IBEAM,
|
||||
CURSOR_HW_CROSS,
|
||||
CURSOR_HW_HAND,
|
||||
CURSOR_HW_HDRAG,
|
||||
CURSOR_HW_VDRAG,
|
||||
CURSOR_HW_HAND,
|
||||
CURSOR_HW_CROSS,
|
||||
CURSOR_SW_AUTO,
|
||||
};
|
||||
void window_cursor_shape(unsigned shape);
|
||||
const char *window_clipboard();
|
||||
void window_setclipboard(const char *text);
|
||||
]])
|
||||
local _M = {}
|
||||
function _M.vec2(x,y)
|
||||
|
|
|
@ -17,5 +17,8 @@ void main() {
|
|||
}
|
||||
}
|
||||
|
||||
FRAGCOLOR = vec4(border_color.rgb, outline * border_color.a); // mix(texel, border_color, outline * border_color.a);
|
||||
}
|
||||
if( outline == 0.0 )
|
||||
discard;
|
||||
|
||||
FRAGCOLOR = vec4(border_color.rgb, outline * border_color.a);
|
||||
}
|
||||
|
|
4070
engine/joint/v4k.h
4070
engine/joint/v4k.h
File diff suppressed because it is too large
Load Diff
|
@ -1491,8 +1491,12 @@ struct dir {
|
|||
|
||||
// ---
|
||||
|
||||
#if !defined(S_ISDIR)
|
||||
# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
|
||||
#ifndef S_ISDIR
|
||||
#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
|
||||
#endif
|
||||
|
||||
#ifndef S_ISREG
|
||||
#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
|
||||
#endif
|
||||
|
||||
int dir_yield(dir *d, const char *pathfile, char *name, int namelen) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -25275,6 +25275,7 @@ nk_selectable_text(struct nk_context *ctx, const char *str, int len,
|
|||
state = nk_widget(&bounds, ctx);
|
||||
if (!state) return 0;
|
||||
in = (state == NK_WIDGET_ROM || layout->flags & NK_WINDOW_ROM) ? 0 : &ctx->input;
|
||||
|
||||
return nk_do_selectable(&ctx->last_widget_state, &win->buffer, bounds,
|
||||
str, len, align, value, &style->selectable, in, style->font);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,400 @@
|
|||
// file browser for nuklear, based on https://github.com/vurtun/nuklear/blob/master/example/file_browser.c (public domain)
|
||||
// - rlyeh, public domain
|
||||
//
|
||||
// changelog:
|
||||
// - ported to FWK api
|
||||
// - namespaced symbols
|
||||
// - diverse win32 fixes
|
||||
// - adaptive cols/rows
|
||||
// - removed nk_begin()/nk_end() pairs
|
||||
// - dangling nk_group_begin/end() pairs
|
||||
// - simplified file<->media_group concept
|
||||
// - minor cosmetics
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h> // _getcwd()
|
||||
#else
|
||||
#include <unistd.h> // getcwd()
|
||||
#include <pwd.h> // getpwuid()
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
#define BROWSER_PRINTF(...) do {} while(0)
|
||||
#else
|
||||
#define BROWSER_PRINTF printf
|
||||
#endif
|
||||
|
||||
enum browser_groups {
|
||||
BROWSER_FOLDER,
|
||||
BROWSER_HOME,
|
||||
BROWSER_DESKTOP,
|
||||
BROWSER_COMPUTER,
|
||||
BROWSER_PROJECT,
|
||||
BROWSER_MAXFOLDERS,
|
||||
|
||||
BROWSER_MAXTYPES = 64,
|
||||
};
|
||||
|
||||
struct browser_media_group {
|
||||
unsigned icon;
|
||||
const char *extensions;
|
||||
};
|
||||
|
||||
struct browser_media {
|
||||
int font;
|
||||
int icon_sheet;
|
||||
struct nk_image custom_folders[BROWSER_MAXFOLDERS];
|
||||
struct nk_image custom_files[BROWSER_MAXTYPES];
|
||||
struct browser_media_group group[BROWSER_MAXTYPES];
|
||||
} media = {0};
|
||||
|
||||
void browser_config_dir(struct nk_image icon, unsigned counter) {
|
||||
if( counter < BROWSER_MAXFOLDERS ) {
|
||||
media.custom_folders[ counter ] = icon;
|
||||
}
|
||||
}
|
||||
|
||||
void browser_config_type(struct nk_image icon, const char *extensions) {
|
||||
static int counter = 0;
|
||||
if( counter < BROWSER_MAXTYPES ) {
|
||||
media.custom_files[ counter ] = icon;
|
||||
media.group[ counter ].icon = counter;
|
||||
media.group[ counter ].extensions = extensions;
|
||||
++counter;
|
||||
}
|
||||
}
|
||||
|
||||
#define BROWSER_MAX_PATH 512
|
||||
struct browser {
|
||||
/* path */
|
||||
char file[BROWSER_MAX_PATH]; // selection
|
||||
char directory[BROWSER_MAX_PATH]; // current cwd while browsing
|
||||
|
||||
char home[BROWSER_MAX_PATH];
|
||||
char desktop[BROWSER_MAX_PATH];
|
||||
char computer[BROWSER_MAX_PATH];
|
||||
char project[BROWSER_MAX_PATH]; // cwd when first invoked
|
||||
|
||||
/* directory content */
|
||||
array(char*) files;
|
||||
array(char*) directories;
|
||||
size_t file_count;
|
||||
size_t dir_count;
|
||||
|
||||
/* view mode */
|
||||
bool listing;
|
||||
float zooming;
|
||||
};
|
||||
|
||||
static struct nk_image* media_icon_for_file(const char *file) {
|
||||
/* extract extension .xxx from file */
|
||||
char *ext = strrchr(file, '.');
|
||||
if( ext && strlen(ext) < 16 ) {
|
||||
char ext_dot[16+1];
|
||||
snprintf(ext_dot, 16, "%s.", ext);
|
||||
/* check for all file definition of all groups for fitting extension. skip first group (default) */
|
||||
for (int i = 1; i < BROWSER_MAXTYPES && media.group[i].extensions; ++i) {
|
||||
if( strstri(media.group[i].extensions, ext_dot) ) {
|
||||
return &media.custom_files[ media.group[i].icon ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return first (default) group
|
||||
return &media.custom_files[0];
|
||||
}
|
||||
|
||||
|
||||
static void browser_reload_directory_content(struct browser *browser, const char *path) {
|
||||
if(path[0] == '\0') path = va("./");
|
||||
if(!strend(path, "/")) path = va("%s/", path);
|
||||
|
||||
for(int i = 0; i < array_count(browser->files); ++i) FREE(browser->files[i]);
|
||||
for(int i = 0; i < array_count(browser->directories); ++i) FREE(browser->directories[i]);
|
||||
|
||||
array_resize(browser->files, 0);
|
||||
array_resize(browser->directories, 0);
|
||||
|
||||
BROWSER_PRINTF("searching at %s\n", path);
|
||||
|
||||
const char **list = file_list(path, "*");
|
||||
for( int i = 0; list[i]; ++i ) {
|
||||
|
||||
char *absolute = file_pathabs(ifndef(win32, list[i], va("%s/%s", path, list[i]))); // ../dir/./file.ext -> c:/prj/dir/file.ext
|
||||
BROWSER_PRINTF("%s->%s %d->", list[i], absolute, file_directory(absolute) );
|
||||
|
||||
if( file_directory(absolute) ) {
|
||||
// remove last '/' if present. ok to overwrite absolute var, file_*() API returns writeable strings.
|
||||
char *dir = absolute; if( dir[ strlen(dir) - 1 ] == '/' ) dir[ strlen(dir) - 1 ] = '\0';
|
||||
|
||||
dir = file_name(dir); // /home/rlyeh/prj/fwk/art -> art
|
||||
BROWSER_PRINTF("%s\n", dir);
|
||||
|
||||
if( dir[0] != '.' ) // skip special files, folders and internal files like .git or .art.zip
|
||||
array_push(browser->directories, STRDUP(dir));
|
||||
} else {
|
||||
const char *fname = file_name(absolute);
|
||||
|
||||
BROWSER_PRINTF("%s\n", fname);
|
||||
|
||||
if( fname[0] != '.' ) // skip special files, folders and internal files like .git or .art.zip
|
||||
array_push(browser->files, STRDUP(fname));
|
||||
}
|
||||
}
|
||||
|
||||
browser->file_count = array_count(browser->files);
|
||||
browser->dir_count = array_count(browser->directories);
|
||||
}
|
||||
|
||||
static void browser_chdir_and_reload_directory_content(struct browser *browser, const char *path) {
|
||||
if( path != browser->directory ) strncpy(browser->directory, path, BROWSER_MAX_PATH);
|
||||
browser_reload_directory_content(browser, path);
|
||||
}
|
||||
|
||||
static void browser_init(struct browser *browser) {
|
||||
memset(browser, 0, sizeof(*browser));
|
||||
{
|
||||
/* load files and sub-directory list */
|
||||
const char *home = getenv("HOME");
|
||||
#ifdef _WIN32
|
||||
if (!home) home = getenv("USERPROFILE");
|
||||
#else
|
||||
if (!home) home = getpwuid(getuid())->pw_dir;
|
||||
#endif
|
||||
snprintf(browser->home, BROWSER_MAX_PATH, "%s/", home);
|
||||
snprintf(browser->desktop, BROWSER_MAX_PATH, "%s/Desktop/", home);
|
||||
snprintf(browser->computer, BROWSER_MAX_PATH, "%s", ifdef(win32, va("%.*s", 3, getenv("windir")), "/"));
|
||||
{
|
||||
ifdef(win32, _getcwd, getcwd)(browser->project, sizeof(browser->project) - 1); // -1 == room for '/'
|
||||
strcat(browser->project, "/");
|
||||
}
|
||||
|
||||
BROWSER_PRINTF("%s\n", browser->home);
|
||||
BROWSER_PRINTF("%s\n", browser->desktop);
|
||||
BROWSER_PRINTF("%s\n", browser->computer);
|
||||
BROWSER_PRINTF("%s\n", browser->project);
|
||||
|
||||
browser_chdir_and_reload_directory_content(browser, browser->project);
|
||||
}
|
||||
}
|
||||
|
||||
static void browser_free(struct browser *browser) {
|
||||
for(int i = 0; i < array_count(browser->files); ++i) FREE(browser->files[i]);
|
||||
for(int i = 0; i < array_count(browser->directories); ++i) FREE(browser->directories[i]);
|
||||
array_free(browser->files);
|
||||
array_free(browser->directories);
|
||||
memset(browser, 0, sizeof(*browser));
|
||||
}
|
||||
|
||||
static int browser_run(struct nk_context *ctx, struct browser *browser, int windowed, struct nk_rect total_space) {
|
||||
int clicked = 0;
|
||||
|
||||
static float ratio[] = {0.25f, NK_UNDEFINED};
|
||||
|
||||
float spacing_x = ctx->style.window.spacing.x;
|
||||
|
||||
/* output path directory selector in the menubar */
|
||||
ctx->style.window.spacing.x = 0;
|
||||
if( windowed ) nk_menubar_begin(ctx);
|
||||
{
|
||||
char *d = browser->directory;
|
||||
#ifdef _WIN32
|
||||
char *begin = d;
|
||||
#else
|
||||
char *begin = d + 1;
|
||||
#endif
|
||||
nk_layout_row_template_begin(ctx, 25);
|
||||
nk_layout_row_template_push_variable(ctx, 40);
|
||||
nk_layout_row_template_push_variable(ctx, 40);
|
||||
nk_layout_row_template_push_variable(ctx, 40);
|
||||
nk_layout_row_template_end(ctx);
|
||||
|
||||
if (nk_button_label(ctx, !browser->listing ? ICON_MD_LIST : ICON_MD_GRID_VIEW)) {
|
||||
browser->listing ^= 1;
|
||||
}
|
||||
|
||||
while (*d++) {
|
||||
if (*d == '/') {
|
||||
*d = '\0';
|
||||
if (nk_button_label(ctx, va("%s" ICON_MD_ARROW_RIGHT, file_name(begin)))) {
|
||||
*d++ = '/'; *d = '\0';
|
||||
browser_chdir_and_reload_directory_content(browser, browser->directory);
|
||||
break;
|
||||
}
|
||||
*d = '/';
|
||||
begin = d + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( windowed ) nk_menubar_end(ctx);
|
||||
ctx->style.window.spacing.x = spacing_x;
|
||||
|
||||
if(nk_window_has_focus(ctx))
|
||||
browser->zooming = clampf( browser->zooming + input_diff(MOUSE_W) * 0.1, 1, 3);
|
||||
|
||||
bool compact = 0, tiny = browser->listing; // compact, no left panel. tiny, no large icons
|
||||
size_t cols = total_space.w / (100 * browser->zooming);
|
||||
int icon_height = (67 * browser->zooming) * (tiny ? 0.33 : 1.); // icon height (96) + button padding (??). originally: 135
|
||||
/**/ if( tiny ) cols = (int)cols+1.5, cols /= 2, compact = total_space.w < 500; // cols <= 2;
|
||||
else cols = (int)cols+1, compact = total_space.w < 500; // cols <= 5;
|
||||
if( cols < 1 ) cols=1;
|
||||
|
||||
/* window layout */
|
||||
nk_layout_row(ctx, NK_DYNAMIC, total_space.h, compact ? 1 : 2, compact ? ratio+1 : ratio);
|
||||
if( !compact )
|
||||
if( nk_group_begin(ctx, "Special", NK_WINDOW_NO_SCROLLBAR) ) {
|
||||
nk_layout_row_dynamic(ctx, 40, 1);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_HOME],"Home",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->home);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_DESKTOP],"Desktop",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->desktop);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_COMPUTER],"Computer",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->computer);
|
||||
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_PROJECT],"Project",NK_TEXT_RIGHT))
|
||||
browser_chdir_and_reload_directory_content(browser, browser->project);
|
||||
|
||||
nk_group_end(ctx);
|
||||
}
|
||||
|
||||
/* output directory content window */
|
||||
if(nk_group_begin(ctx, "Content", windowed ? NK_WINDOW_NO_SCROLLBAR : 0)) {
|
||||
int index = -1;
|
||||
size_t i = 0, j = 0, k = 0;
|
||||
size_t rows = 0;
|
||||
size_t count = browser->dir_count + browser->file_count;
|
||||
|
||||
rows = count / cols;
|
||||
for (i = 0; i <= rows; i += 1) {
|
||||
if(!tiny)
|
||||
{size_t n = j + cols;
|
||||
nk_layout_row_dynamic(ctx, icon_height, (int)cols);
|
||||
for (; j < count && j < n; ++j) {
|
||||
/* draw one row of icons */
|
||||
if (j < browser->dir_count) {
|
||||
/* draw and execute directory buttons */
|
||||
if (nk_button_image(ctx,media.custom_folders[BROWSER_FOLDER]))
|
||||
index = (int)j;
|
||||
} else {
|
||||
/* draw and execute files buttons */
|
||||
struct nk_image *icon;
|
||||
size_t fileIndex = ((size_t)j - browser->dir_count);
|
||||
icon = media_icon_for_file(browser->files[fileIndex]);
|
||||
if (nk_button_image(ctx, *icon)) {
|
||||
snprintf(browser->file, BROWSER_MAX_PATH, "%s%s", browser->directory, browser->files[fileIndex]);
|
||||
clicked = 1;
|
||||
}
|
||||
}
|
||||
}}
|
||||
if(!tiny)
|
||||
{size_t n = k + cols;
|
||||
nk_layout_row_dynamic(ctx, 20, (int)cols);
|
||||
for (; k < count && k < n; k++) {
|
||||
/* draw one row of labels */
|
||||
if (k < browser->dir_count) {
|
||||
nk_label(ctx, browser->directories[k], NK_TEXT_CENTERED);
|
||||
} else {
|
||||
size_t t = k-browser->dir_count;
|
||||
nk_label(ctx,browser->files[t],NK_TEXT_CENTERED);
|
||||
}
|
||||
}}
|
||||
if(tiny)
|
||||
{size_t n = j + cols;
|
||||
nk_layout_row_dynamic(ctx, icon_height, (int)cols);
|
||||
for (; j < count && j < n; ++j) {
|
||||
/* draw one row of icons */
|
||||
if (j < browser->dir_count) {
|
||||
/* draw and execute directory buttons */
|
||||
if (nk_button_image_label(ctx,media.custom_folders[BROWSER_FOLDER], browser->directories[j],NK_TEXT_RIGHT))
|
||||
index = (int)j;
|
||||
} else {
|
||||
/* draw and execute files buttons */
|
||||
struct nk_image *icon;
|
||||
size_t fileIndex = ((size_t)j - browser->dir_count);
|
||||
icon = media_icon_for_file(browser->files[fileIndex]);
|
||||
size_t t = j-browser->dir_count;
|
||||
if (nk_button_image_label(ctx, *icon, browser->files[t],NK_TEXT_RIGHT)) {
|
||||
snprintf(browser->file, BROWSER_MAX_PATH, "%s%s", browser->directory, browser->files[fileIndex]);
|
||||
clicked = 1;
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
bool has_focus = nk_window_has_focus(ctx); // @fixme: move out of loop
|
||||
bool has_popups = ui_popups(); // @fixme: move out of loop
|
||||
if( !has_popups && has_focus ) {
|
||||
struct nk_rect bounds = nk_widget_bounds(ctx);
|
||||
if (nk_input_is_mouse_hovering_rect(&ctx->input, bounds) ) {
|
||||
|
||||
char *name = j < browser->dir_count ? browser->directories[j] : browser->files[j-browser->dir_count];
|
||||
|
||||
char fullpath[PATH_MAX];
|
||||
snprintf(fullpath, PATH_MAX, "%s%s", browser->directory, name);
|
||||
|
||||
struct stat t = {0};
|
||||
if( stat( fullpath, &t ) != -1 ) {
|
||||
char tooltip[256];
|
||||
snprintf(tooltip, 256,
|
||||
"Path: %s\n"
|
||||
"Type: %lld\n" // file type and mode
|
||||
"Size: %lld\n" // file size
|
||||
"Owner: %lld\n" // user ID of file owner
|
||||
"Modified: %s (%lld)", // last modification date
|
||||
name, (int64_t)t.st_mode, (int64_t)t.st_size, (int64_t)t.st_uid, ctime(&t.st_mtime), (int64_t)t.st_mtime
|
||||
);
|
||||
nk_tooltip(ctx, tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}}
|
||||
}
|
||||
|
||||
if (index != -1) {
|
||||
BROWSER_PRINTF("%s + %s = ", browser->directory, browser->directories[index]);
|
||||
size_t n = strlen(browser->directory);
|
||||
snprintf(browser->directory + n, BROWSER_MAX_PATH - n, "%s/", browser->directories[index]);
|
||||
BROWSER_PRINTF("%s\n", browser->directory);
|
||||
browser_chdir_and_reload_directory_content(browser, browser->directory);
|
||||
}
|
||||
nk_group_end(ctx);
|
||||
}
|
||||
|
||||
return clicked;
|
||||
}
|
||||
|
||||
static struct nk_image icon_load(const char *filename) {
|
||||
texture_t t = texture(filename, 0);
|
||||
return nk_image_id((int)t.id);
|
||||
}
|
||||
|
||||
static struct nk_image icon_load_rect(unsigned id, unsigned w, unsigned h, unsigned wcell, unsigned hcell, unsigned col, unsigned row) {
|
||||
return nk_subimage_id((int)id, w, h, (struct nk_rect){ wcell * col, hcell * row, wcell, hcell });
|
||||
}
|
||||
|
||||
|
||||
/* demo:
|
||||
|
||||
struct browser browser = {0};
|
||||
browser_init(&browser);
|
||||
browser_config_dir(nk_image, BROWSER_HOME);
|
||||
browser_config_dir(nk_image, BROWSER_PROJECT);
|
||||
// [...]
|
||||
browser_config_type(nk_image, ".ext1.ext2.ext3.");
|
||||
browser_config_type(nk_image, ".ext1.ext2.ext3.");
|
||||
browser_config_type(nk_image, ".ext1.ext2.ext3.");
|
||||
// [...]
|
||||
|
||||
[...]
|
||||
|
||||
if( nk_begin(ctx, "window", ...) ) {
|
||||
struct nk_rect total_space = nk_window_get_content_region(ctx);
|
||||
if( browser_run(ctx, &browser, 0, total_space) ) {
|
||||
puts( browser->directory );
|
||||
puts( browser->file );
|
||||
}
|
||||
}
|
||||
nk_end();
|
||||
*/
|
|
@ -1328,7 +1328,7 @@ STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int
|
|||
#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8)
|
||||
STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)
|
||||
{
|
||||
return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);
|
||||
return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -1345,8 +1345,8 @@ static FILE *stbi__fopen(char const *filename, char const *mode)
|
|||
return 0;
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1400
|
||||
if (0 != _wfopen_s(&f, wFilename, wMode))
|
||||
f = 0;
|
||||
if (0 != _wfopen_s(&f, wFilename, wMode))
|
||||
f = 0;
|
||||
#else
|
||||
f = _wfopen(wFilename, wMode);
|
||||
#endif
|
||||
|
@ -3436,8 +3436,8 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j)
|
|||
m = stbi__get_marker(j);
|
||||
} else {
|
||||
if (!stbi__process_marker(j, m)) return 1;
|
||||
m = stbi__get_marker(j);
|
||||
}
|
||||
m = stbi__get_marker(j);
|
||||
}
|
||||
}
|
||||
if (j->progressive)
|
||||
stbi__jpeg_finish(j);
|
||||
|
@ -5330,7 +5330,7 @@ static int stbi__png_is16(stbi__context *s)
|
|||
stbi__png p;
|
||||
p.s = s;
|
||||
if (!stbi__png_info_raw(&p, NULL, NULL, NULL))
|
||||
return 0;
|
||||
return 0;
|
||||
if (p.depth != 16) {
|
||||
stbi__rewind(p.s);
|
||||
return 0;
|
||||
|
@ -5574,7 +5574,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req
|
|||
// negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn
|
||||
// ensures the number computed in the second half of the test can't overflow.
|
||||
if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) {
|
||||
return stbi__errpuc("bad offset", "Corrupt BMP");
|
||||
return stbi__errpuc("bad offset", "Corrupt BMP");
|
||||
} else {
|
||||
stbi__skip(s, info.offset - bytes_read_so_far);
|
||||
}
|
||||
|
@ -7339,7 +7339,7 @@ static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)
|
|||
info.all_a = 255;
|
||||
p = stbi__bmp_parse_header(s, &info);
|
||||
if (p == NULL) {
|
||||
stbi__rewind( s );
|
||||
stbi__rewind( s );
|
||||
return 0;
|
||||
}
|
||||
if (x) *x = s->img_x;
|
||||
|
@ -7532,7 +7532,7 @@ static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req
|
|||
if (ri->bits_per_channel == 16) {
|
||||
out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y);
|
||||
} else {
|
||||
out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
|
||||
out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
|
||||
}
|
||||
if (out == NULL) return out; // stbi__convert_format frees input on failure
|
||||
}
|
||||
|
|
|
@ -493,11 +493,11 @@ static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, c
|
|||
{
|
||||
if (comp != 4) {
|
||||
// write RGB bitmap
|
||||
int pad = (-x*3) & 3;
|
||||
return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad,
|
||||
"11 4 22 4" "4 44 22 444444",
|
||||
'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header
|
||||
40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header
|
||||
int pad = (-x*3) & 3;
|
||||
return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad,
|
||||
"11 4 22 4" "4 44 22 444444",
|
||||
'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header
|
||||
40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header
|
||||
} else {
|
||||
// RGBA bitmaps need a v4 header
|
||||
// use BI_BITFIELDS mode with 32bpp and alpha mask
|
||||
|
|
|
@ -2594,14 +2594,14 @@ static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A,
|
|||
float k00,k11;
|
||||
float l00,l11;
|
||||
|
||||
k00 = z[-0] - z[-8];
|
||||
k11 = z[-1] - z[-9];
|
||||
k00 = z[-0] - z[ -8];
|
||||
k11 = z[-1] - z[ -9];
|
||||
l00 = z[-2] - z[-10];
|
||||
l11 = z[-3] - z[-11];
|
||||
z[-0] = z[-0] + z[-8];
|
||||
z[-1] = z[-1] + z[-9];
|
||||
z[ -2] = z[ -2] + z[-10];
|
||||
z[ -3] = z[ -3] + z[-11];
|
||||
z[ -0] = z[-0] + z[ -8];
|
||||
z[ -1] = z[-1] + z[ -9];
|
||||
z[ -2] = z[-2] + z[-10];
|
||||
z[ -3] = z[-3] + z[-11];
|
||||
z[ -8] = k00;
|
||||
z[ -9] = k11;
|
||||
z[-10] = (l00+l11) * A2;
|
||||
|
@ -3661,7 +3661,7 @@ static int start_decoder(vorb *f)
|
|||
f->comment_list = NULL;
|
||||
if (f->comment_list_length > 0)
|
||||
{
|
||||
f->comment_list = (char**)setup_malloc(f, sizeof(char*) * (f->comment_list_length));
|
||||
f->comment_list = (char**) setup_malloc(f, sizeof(char*) * (f->comment_list_length));
|
||||
if (f->comment_list == NULL) return error(f, VORBIS_outofmem);
|
||||
}
|
||||
|
||||
|
|
|
@ -98,18 +98,6 @@
|
|||
#include "v4k"
|
||||
#endif
|
||||
|
||||
#define do_threadlock(mutexptr) \
|
||||
for( int init_ = !!(mutexptr) || (thread_mutex_init( (mutexptr) = CALLOC(1, sizeof(thread_mutex_t)) ), 1); init_; init_ = 0) \
|
||||
for( int lock_ = (thread_mutex_lock( mutexptr ), 1); lock_; lock_ = (thread_mutex_unlock( mutexptr ), 0) )
|
||||
|
||||
API void *ui_handle();
|
||||
#define ui_push_hspace(px) \
|
||||
(int xx = px; xx; xx = 0) \
|
||||
for(struct nk_context *ctx = (struct nk_context*)ui_handle(); ctx; ctx = 0 ) \
|
||||
for(struct nk_panel *layout = ui_ctx->current->layout; layout; ) \
|
||||
for( xx = (layout->at_x += px, layout->bounds.w -= px, 0); layout; layout->at_x -= px, layout->bounds.w += px, layout = 0 )
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// C files
|
||||
|
||||
|
@ -160,10 +148,10 @@ API void *ui_handle();
|
|||
|
||||
{{FILE:v4k_scene.c}}
|
||||
|
||||
{{FILE:v4k_time.c}}
|
||||
|
||||
{{FILE:v4k_system.c}}
|
||||
|
||||
{{FILE:v4k_time.c}}
|
||||
|
||||
{{FILE:v4k_profile.c}}
|
||||
|
||||
{{FILE:v4k_video.c}}
|
||||
|
|
|
@ -118,10 +118,10 @@ extern "C" {
|
|||
|
||||
{{FILE:v4k_editor.h}}
|
||||
|
||||
{{FILE:v4k_font.h}}
|
||||
|
||||
{{FILE:v4k_file.h}}
|
||||
|
||||
{{FILE:v4k_font.h}}
|
||||
|
||||
{{FILE:v4k_input.h}}
|
||||
|
||||
{{FILE:v4k_memory.h}}
|
||||
|
@ -146,10 +146,10 @@ extern "C" {
|
|||
|
||||
{{FILE:v4k_string.h}}
|
||||
|
||||
{{FILE:v4k_time.h}}
|
||||
|
||||
{{FILE:v4k_system.h}}
|
||||
|
||||
{{FILE:v4k_time.h}}
|
||||
|
||||
{{FILE:v4k_ui.h}}
|
||||
|
||||
{{FILE:v4k_video.h}}
|
||||
|
@ -170,7 +170,7 @@ extern "C" {
|
|||
#include <emscripten/html5.h>
|
||||
#define gladLoadGL(func) (glewExperimental = true, glewInit() == GLEW_OK)
|
||||
#else
|
||||
#if is(win32) /*&& is(tcc)*/ // && WITH_DLL
|
||||
#if is(win32) /*&& is(tcc)*/ // && ENABLE_DLL
|
||||
#ifdef GLAD_API_CALL
|
||||
#undef GLAD_API_CALL
|
||||
#endif
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#define JO_MPEG_COMPONENTS 3 // jo_mpeg
|
||||
#define JSON5_C // json5
|
||||
#define LUA_IMPL // lua544
|
||||
#define LUA_USE_POPEN 1 // for lite editor
|
||||
#define MINIAUDIO_IMPLEMENTATION // miniaudio
|
||||
#define MA_NO_FLAC // miniaudio
|
||||
#define NK_GLFW_GL3_IMPLEMENTATION // nuklear
|
||||
|
@ -52,7 +53,7 @@
|
|||
#define BQ_WEBSOCKET_IMPLEMENTATION // websocket
|
||||
#define XML_C // xml
|
||||
#ifdef __APPLE__
|
||||
#define MA_NO_RUNTIME_LINKING // miniaudio
|
||||
#define MA_NO_RUNTIME_LINKING // miniaudio osx
|
||||
#define _GLFW_COCOA // glfw osx
|
||||
#elif defined _WIN32
|
||||
#define _GLFW_WIN32 // glfw win32
|
||||
|
@ -101,7 +102,6 @@ errno_t fopen_s(
|
|||
);
|
||||
#endif
|
||||
|
||||
|
||||
//---
|
||||
{{FILE:3rd_glfw3.h}}
|
||||
#undef timeGetTime
|
||||
|
@ -171,18 +171,28 @@ static char *ui_filter = 0;
|
|||
#undef g
|
||||
{{FILE:3rd_polychop.h}}
|
||||
#define expr expr2 // 3rd_lua.h
|
||||
#define EVAL_EXTEND_CONSTANTS \
|
||||
for( int vk = input_enum(id), *once = &vk; once; once = 0) \
|
||||
if( vk >= 0 ) push(ev, vk);
|
||||
#define EVAL_EXTEND_FUNCTIONS \
|
||||
/**/ if(!strcmp(id, "input") && nargs ==1) push(ev, input(pop(ev))); \
|
||||
else if(!strcmp(id, "down") && nargs ==1) push(ev, input_down(pop(ev))); \
|
||||
else if(!strcmp(id, "held") && nargs ==1) push(ev, input_held(pop(ev))); \
|
||||
else if(!strcmp(id, "up") && nargs ==1) push(ev, input_up(pop(ev))); \
|
||||
else if(!strcmp(id, "idle") && nargs ==1) push(ev, input_idle(pop(ev)));
|
||||
{{FILE:3rd_eval.h}}
|
||||
// #define SQLITE_OMIT_LOAD_EXTENSION
|
||||
// #define SQLITE_CORE 1
|
||||
// #define SQLITE_DEBUG 1
|
||||
// #define Token SQToken
|
||||
// #define Table SQTable
|
||||
// #define rehash sqlite3__rehash
|
||||
// #undef NB
|
||||
//{{FILE/*:*/3rd_sqlite3.c}}
|
||||
// #undef Token
|
||||
// #undef Table
|
||||
// #undef rehash
|
||||
// #undef NB
|
||||
// #undef threadid
|
||||
{{FILE:3rd_luadebugger.h}}
|
||||
//#define SQLITE_OMIT_LOAD_EXTENSION
|
||||
//#define SQLITE_CORE 1
|
||||
//#define SQLITE_DEBUG 1
|
||||
//#define Token SQToken
|
||||
//#define Table SQTable
|
||||
//#define rehash sqlite3__rehash
|
||||
//#undef NB
|
||||
//{ {FILE:3rd_sqlite3.c}}
|
||||
//#undef Token
|
||||
//#undef Table
|
||||
//#undef rehash
|
||||
//#undef NB
|
||||
//#undef threadid
|
||||
#endif // V4K_3RD
|
||||
|
|
|
@ -343,7 +343,7 @@ int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i
|
|||
#error ASTAR_POS_INDEX(p) should specify macro to map position to index
|
||||
#endif
|
||||
|
||||
#ifndef ASTAR_MAX_INDEX
|
||||
#ifndef ASTAR_MAX_INDEX
|
||||
#error ASTAR_MAX_INDEX should specify max count of indices the position can map to
|
||||
#endif
|
||||
|
||||
|
@ -434,7 +434,7 @@ int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i
|
|||
i = p; \
|
||||
p = (i - 1) / 2; \
|
||||
} \
|
||||
} while (0)
|
||||
} while (0)
|
||||
|
||||
#define ASTAR_HEAP_POP() \
|
||||
do { \
|
||||
|
@ -625,7 +625,7 @@ int pathfind_astar(int width, int height, const unsigned* map, vec2i src, vec2i
|
|||
// [ ] CompareKeys(keyVar1, operator < <= > >= == !=, keyVar2)
|
||||
// [ ] SetTags(names=blank,cooldownTime=inf,bIsCooldownAdditive=false)
|
||||
// [ ] HasTags(names=blank,bAllRequired=true)
|
||||
// [ ] PushToStack(keyVar,itemObj): creates a new stack if one doesn’t exist, and stores it in the passed variable name, and then pushes ‘item’ object onto it.
|
||||
// [ ] PushToStack(keyVar,itemObj): creates a new stack if one doesnt exist, and stores it in the passed variable name, and then pushes item object onto it.
|
||||
// [ ] PopFromStack(keyVar,itemVar): pop pops an item off the stack, and stores it in the itemVar variable, failing if the stack is already empty.
|
||||
// [ ] IsEmptyStack(keyVar): checks if the stack passed is empty and returns success if it is, and failure if its not.
|
||||
// [ ] Communication Node: This is a type of action node that allows an AI agent to communicate with other agents or entities in the game world. The node takes an input specifying the message to be communicated and the recipient(s) of the message (wildmask,l/p/f/g prefixes). The node then sends the message to the designated recipient(s) and returns success when the communication is completed. This node can be useful for implementing behaviors that require the AI agent to coordinate with other agents or to convey information to the player. It could use a radius argument to specify the maximum allowed distance for the recipients.
|
||||
|
|
|
@ -1,28 +1,35 @@
|
|||
// dll ------------------------------------------------------------------------
|
||||
|
||||
/* deprecated
|
||||
#if is(win32)
|
||||
# include <winsock2.h>
|
||||
# define dlopen(name,mode) (void*)( (name) ? LoadLibraryA(name) : GetModuleHandle(NULL))
|
||||
# define dlsym(handle,symbol) GetProcAddress((HMODULE)handle, symbol )
|
||||
# define dlopen(name,flags) (void*)( (name) ? LoadLibraryA(name) : GetModuleHandleA(NULL))
|
||||
# define dlsym(handle,symbol) GetProcAddress((HMODULE)(handle), symbol )
|
||||
# define dlclose(handle) 0
|
||||
#else
|
||||
# include <dlfcn.h>
|
||||
# define dlopen(name,flags) (void*)( (name) ? dlopen(name, flags) : NULL )
|
||||
# define dlsym(handle,symbol) dlsym( (handle) ? (handle) : ifdef(osx,RTLD_SELF,NULL), symbol )
|
||||
#endif
|
||||
|
||||
void* dll(const char *filename, const char *symbol) {
|
||||
/*
|
||||
char *buf, *base = file_name(filename);
|
||||
if( file_exists(buf = va("%s", base)) ||
|
||||
file_exists(buf = va("%s.dll", base)) ||
|
||||
file_exists(buf = va("%s.so", base)) ||
|
||||
file_exists(buf = va("lib%s.so", base)) ||
|
||||
file_exists(buf = va("%s.dylib", base)) ) {
|
||||
filename = buf;
|
||||
}
|
||||
*/
|
||||
void *dll = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
|
||||
dll = dll ? dlsym(dll, symbol) : 0;
|
||||
return dll;
|
||||
|
||||
void* dll(const char *fname, const char *symbol) {
|
||||
if( fname && !file_exist(fname) ) {
|
||||
char *buf, *path = file_path(fname), *base = file_base(fname);
|
||||
if( file_exist(buf = va("%s%s.dll", path, base)) ||
|
||||
file_exist(buf = va("%s%s.so", path, base)) ||
|
||||
file_exist(buf = va("%slib%s.so", path, base)) ||
|
||||
file_exist(buf = va("%s%s.dylib", path, base)) ) {
|
||||
fname = (const char *)buf;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#if is(win32)
|
||||
return (void*)GetProcAddress(fname ? LoadLibraryA(fname) : GetModuleHandleA(NULL), symbol);
|
||||
#else
|
||||
return dlsym(fname ? dlopen(fname, RTLD_NOW|RTLD_LOCAL) : ifdef(osx, RTLD_SELF, NULL), symbol);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if 0 // demo: cl demo.c /LD && REM dll
|
||||
|
@ -210,3 +217,72 @@ bool script_tests() {
|
|||
}
|
||||
|
||||
#undef XMACRO
|
||||
|
||||
// script v2 ------------------------------------------------------------------
|
||||
|
||||
#define luaL_dostringsafe(L, str) \
|
||||
luaL_dostring(L, \
|
||||
"xpcall(function()\n" \
|
||||
str \
|
||||
"end, function(err)\n" \
|
||||
" print('Error: ' .. tostring(err))\n" \
|
||||
" print(debug.traceback(nil, 2))\n" \
|
||||
" if core and core.on_error then\n" \
|
||||
" pcall(core.on_error, err)\n" \
|
||||
" end\n" \
|
||||
" os.exit(1)\n" \
|
||||
"end)" \
|
||||
);
|
||||
|
||||
static int f_vfs_read(lua_State *L) {
|
||||
char *file = file_normalize(luaL_checkstring(L, 1));
|
||||
if( strbegi(file, app_path()) ) file += strlen(app_path());
|
||||
strswap(file+1, ".", "/");
|
||||
strswap(file+1, "/lua", ".lua");
|
||||
int len; char *data = vfs_load(file, &len);
|
||||
if( len ) {
|
||||
data = memcpy(MALLOC(len+1), data, len), data[len] = 0;
|
||||
//tty_color(data ? GREEN : RED);
|
||||
//printf("%s (%s)\n%s", file, data ? "ok" : "failed", data);
|
||||
//tty_color(0);
|
||||
}
|
||||
return lua_pushstring(L, data), 1; // "\n\tcannot find `%s` within mounted zipfiles", file), 1;
|
||||
}
|
||||
|
||||
// add our zip loader at the end of package.loaders
|
||||
void lua_add_ziploader(lua_State* L) {
|
||||
lua_pushcfunction( L, f_vfs_read );
|
||||
lua_setglobal( L, "vfs_read" );
|
||||
|
||||
luaL_dostringsafe(L,
|
||||
// "package.path = [[;<?>;<<?.lua>>;]]\n" // .. package.path\n"
|
||||
"package.searchers[#package.searchers + 1] = function(libraryname)\n"
|
||||
" for pattern in string.gmatch( package.path, '[^;]+' ) do\n"
|
||||
" local proper_path = string.gsub(pattern, '?', libraryname)\n"
|
||||
" local f = vfs_read(proper_path)\n"
|
||||
" if f ~= nil then\n"
|
||||
" return load(f, proper_path)\n"
|
||||
" end\n"
|
||||
" end\n"
|
||||
" return nil\n"
|
||||
"end\n"
|
||||
);
|
||||
}
|
||||
|
||||
void *script_init_env(unsigned flags) {
|
||||
if( flags & SCRIPT_LUA ) {
|
||||
lua_State *L = luaL_newstate();
|
||||
luaL_openlibs(L);
|
||||
|
||||
if( flags & SCRIPT_DEBUGGER ) {
|
||||
// Register debuggers/inspectors
|
||||
// luaL_dostringsafe(L, "I = require('inspect').inspect\n");
|
||||
dbg_setup_default(L);
|
||||
}
|
||||
|
||||
lua_add_ziploader(L);
|
||||
return L;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -22,3 +22,13 @@ API void script_bind_function(const char *c_name, void *c_function);
|
|||
API void script_call(const char *lua_function);
|
||||
|
||||
API bool script_tests();
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// script framework
|
||||
|
||||
enum {
|
||||
SCRIPT_LUA = 1,
|
||||
SCRIPT_DEBUGGER = 2,
|
||||
};
|
||||
|
||||
API void *script_init_env(unsigned flags);
|
||||
|
|
|
@ -161,7 +161,7 @@ char *file_id(const char *pathfile) {
|
|||
char *ext = strchr(base, '.'); if (ext) ext[0] = '\0'; // remove all extensions
|
||||
#else // extensionless for audio/images only (materials: diffuse.tga and diffuse.png will match)
|
||||
char *ext = strrchr(base, '.'); //if (ext) ext[0] = '\0'; // remove all extensions
|
||||
if(ext) if( strstr(".jpg.png.bmp.tga"".", ext) || strstr(".ogg.mp3.wav.mod.xm.flac"".", ext) || strstr(".mp4.ogv.avi.mkv.wmv.mpg.mpeg"".", ext) ) {
|
||||
if(ext) if( strstr(".jpg.png.bmp.tga.hdr"".", ext) || strstr(".ogg.mp3.wav.mod.xm.flac"".", ext) || strstr(".mp4.ogv.avi.mkv.wmv.mpg.mpeg"".", ext) ) {
|
||||
ext = strchr(base, '.');
|
||||
ext[0] = '\0'; //strcpy(ext, "_xxx");
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ enum INPUT_ENUMS {
|
|||
KEY_PAD1,KEY_PAD2,KEY_PAD3,KEY_PAD4,KEY_PAD5,KEY_PAD6,KEY_PAD7,KEY_PAD8,KEY_PAD9,KEY_PAD0, // beware: complicated on laptops
|
||||
KEY_PADADD,KEY_PADSUB,KEY_PADMUL,KEY_PADDIV,KEY_PADDOT,KEY_PADENTER, // beware: complicated on laptops
|
||||
|
||||
MOUSE_L, MOUSE_M, MOUSE_R,
|
||||
MOUSE_L, MOUSE_M, MOUSE_R, // @todo: MOUSE_CLICKS,
|
||||
GAMEPAD_CONNECTED, GAMEPAD_A, GAMEPAD_B, GAMEPAD_X, GAMEPAD_Y,
|
||||
GAMEPAD_UP, GAMEPAD_DOWN, GAMEPAD_LEFT, GAMEPAD_RIGHT, GAMEPAD_MENU, GAMEPAD_START,
|
||||
GAMEPAD_LB, GAMEPAD_RB, GAMEPAD_LTHUMB, GAMEPAD_RTHUMB,
|
||||
|
|
|
@ -106,6 +106,7 @@ enum TEXTURE_FLAGS {
|
|||
// @fixme
|
||||
TEXTURE_SRGB = 1 << 24,
|
||||
TEXTURE_BGR = 1 << 25,
|
||||
TEXTURE_BGRA = TEXTURE_BGR,
|
||||
TEXTURE_ARRAY = 1 << 26,
|
||||
};
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ char* tempvl(const char *fmt, va_list vl) {
|
|||
static __thread char buf[STACK_ALLOC];
|
||||
#else
|
||||
int heap = 1;
|
||||
static __thread int STACK_ALLOC = 128*1024;
|
||||
static __thread int STACK_ALLOC = 512*1024;
|
||||
static __thread char *buf = 0; if(!buf) buf = REALLOC(0, STACK_ALLOC); // @leak
|
||||
#endif
|
||||
static __thread int cur = 0; //printf("string stack %d/%d\n", cur, STACK_ALLOC);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
API char* tempvl(const char *fmt, va_list);
|
||||
API char* tempva(const char *fmt, ...);
|
||||
#define va(...) (((&printf) || printf(__VA_ARGS__), tempva(__VA_ARGS__))) // vs2015 check trick
|
||||
#define vac (const char*)va
|
||||
|
||||
// string: allocated api (heap). FREE() after use
|
||||
API char* strcatf(char **s, const char *buf);
|
||||
|
|
|
@ -509,8 +509,8 @@ void curve_add(curve_t *c, vec3 p) {
|
|||
array_push(c->points, p);
|
||||
}
|
||||
|
||||
void curve_finish( curve_t *c, int k ) {
|
||||
assert( k > 0 );
|
||||
void curve_end( curve_t *c, int k ) {
|
||||
ASSERT( k > 0 );
|
||||
|
||||
array_free(c->lengths);
|
||||
array_free(c->samples);
|
||||
|
|
|
@ -151,6 +151,6 @@ typedef struct curve_t {
|
|||
|
||||
API curve_t curve();
|
||||
API void curve_add(curve_t *c, vec3 p);
|
||||
API void curve_finish(curve_t *c, int num_points);
|
||||
API void curve_end(curve_t *c, int num_points);
|
||||
API vec3 curve_eval(curve_t *c, float dt, unsigned *color);
|
||||
API void curve_destroy(curve_t *c);
|
||||
|
|
|
@ -2503,7 +2503,7 @@ int ui_browse(const char **output, bool *inlined) {
|
|||
// if(ui_ctx->current) bounds = nk_window_get_bounds(ui_ctx), P(bounds);
|
||||
// if(ui_ctx->current) bounds = nk_window_get_content_region(ui_ctx), P(bounds);
|
||||
// if(ui_ctx->current) nk_layout_peek(&bounds, ui_ctx), P(bounds);
|
||||
// // if(ui_ctx->current) nk_layout_widget_space(&bounds, ui_ctx, ui_ctx->current, nk_false), P(bounds); // note: cant be used within a panel
|
||||
// if(ui_ctx->current) nk_layout_widget_space(&bounds, ui_ctx, ui_ctx->current, nk_false), P(bounds); // note: cant be used within a panel
|
||||
// #undef P
|
||||
|
||||
// panel
|
||||
|
|
|
@ -971,10 +971,10 @@ void window_cursor_shape(unsigned mode) {
|
|||
0,
|
||||
GLFW_ARROW_CURSOR,
|
||||
GLFW_IBEAM_CURSOR,
|
||||
GLFW_CROSSHAIR_CURSOR,
|
||||
GLFW_HAND_CURSOR,
|
||||
GLFW_HRESIZE_CURSOR,
|
||||
GLFW_VRESIZE_CURSOR,
|
||||
GLFW_HAND_CURSOR,
|
||||
GLFW_CROSSHAIR_CURSOR,
|
||||
};
|
||||
do_once {
|
||||
static unsigned pixels[16 * 16] = { 0x01000000 }; // ABGR(le) glfw3 note: A(0x00) means 0xFF for some reason
|
||||
|
@ -1067,4 +1067,17 @@ int window_has_maximize() {
|
|||
return ifdef(ems, 0, glfwGetWindowAttrib(window, GLFW_MAXIMIZED) == GLFW_TRUE);
|
||||
}
|
||||
|
||||
const char *window_clipboard() {
|
||||
return glfwGetClipboardString(window);
|
||||
}
|
||||
void window_setclipboard(const char *text) {
|
||||
glfwSetClipboardString(window, text);
|
||||
}
|
||||
|
||||
static
|
||||
double window_scale() { // ok? @testme
|
||||
float xscale, yscale;
|
||||
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
||||
glfwGetMonitorContentScale(monitor, &xscale, &yscale);
|
||||
return maxi(xscale, yscale);
|
||||
}
|
||||
|
|
|
@ -85,11 +85,14 @@ enum CURSOR_SHAPES {
|
|||
CURSOR_NONE,
|
||||
CURSOR_HW_ARROW, // default
|
||||
CURSOR_HW_IBEAM, // i-beam text cursor
|
||||
CURSOR_HW_CROSS, // crosshair
|
||||
CURSOR_HW_HAND, // hand, clickable
|
||||
CURSOR_HW_HDRAG, // horizontal drag/resize
|
||||
CURSOR_HW_VDRAG, // vertical drag/resize
|
||||
CURSOR_HW_HAND, // hand, clickable
|
||||
CURSOR_HW_CROSS, // crosshair
|
||||
CURSOR_SW_AUTO, // software cursor, ui driven. note: this is the only icon that may be recorded or snapshotted
|
||||
};
|
||||
|
||||
API void window_cursor_shape(unsigned shape);
|
||||
|
||||
API const char *window_clipboard();
|
||||
API void window_setclipboard(const char *text);
|
||||
|
|
2258
engine/v4k
2258
engine/v4k
File diff suppressed because it is too large
Load Diff
1429
engine/v4k.c
1429
engine/v4k.c
File diff suppressed because it is too large
Load Diff
383
engine/v4k.h
383
engine/v4k.h
|
@ -1986,6 +1986,16 @@ API void script_bind_function(const char *c_name, void *c_function);
|
|||
API void script_call(const char *lua_function);
|
||||
|
||||
API bool script_tests();
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// script framework
|
||||
|
||||
enum {
|
||||
SCRIPT_LUA = 1,
|
||||
SCRIPT_DEBUGGER = 2,
|
||||
};
|
||||
|
||||
API void *script_init_env(unsigned flags);
|
||||
#line 0
|
||||
|
||||
#line 1 "engine/split/v4k_editor.h"
|
||||
|
@ -2035,98 +2045,6 @@ API void kit_locale( const char *langcode_iso639_1 ); // set current locale: en
|
|||
API char* kit_translate( const char *id ); // perform a translation, given current locale
|
||||
#line 0
|
||||
|
||||
#line 1 "engine/split/v4k_font.h"
|
||||
// -----------------------------------------------------------------------------
|
||||
// font framework
|
||||
// - rlyeh, public domain
|
||||
|
||||
// font size tags
|
||||
#define FONT_H1 "\1" // largest
|
||||
#define FONT_H2 "\2"
|
||||
#define FONT_H3 "\3"
|
||||
#define FONT_H4 "\4"
|
||||
#define FONT_H5 "\5"
|
||||
#define FONT_H6 "\6" // smallest
|
||||
|
||||
// font color tags
|
||||
#define FONT_COLOR1 "\x10"
|
||||
#define FONT_COLOR2 "\x11"
|
||||
#define FONT_COLOR3 "\x12"
|
||||
#define FONT_COLOR4 "\x13"
|
||||
#define FONT_COLOR5 "\x14"
|
||||
#define FONT_COLOR6 "\x15"
|
||||
#define FONT_COLOR7 "\x16"
|
||||
#define FONT_COLOR8 "\x17"
|
||||
#define FONT_COLOR9 "\x18"
|
||||
#define FONT_COLOR10 "\x19"
|
||||
|
||||
// font face tags
|
||||
#define FONT_FACE1 "\x1a"
|
||||
#define FONT_FACE2 "\x1b"
|
||||
#define FONT_FACE3 "\x1c"
|
||||
#define FONT_FACE4 "\x1d"
|
||||
#define FONT_FACE5 "\x1e"
|
||||
#define FONT_FACE6 "\x1f"
|
||||
|
||||
// font align tags
|
||||
#define FONT_LEFT "\\<"
|
||||
#define FONT_CENTER "\\|"
|
||||
#define FONT_RIGHT "\\>"
|
||||
#define FONT_TOP "\\^"
|
||||
#define FONT_MIDDLE "\\-"
|
||||
#define FONT_BASELINE "\\_"
|
||||
#define FONT_BOTTOM "\\v"
|
||||
|
||||
// font flags
|
||||
enum FONT_FLAGS {
|
||||
// font atlas size
|
||||
FONT_512 = 0x0,
|
||||
FONT_1024 = 0x1,
|
||||
FONT_2048 = 0x2,
|
||||
FONT_4096 = 0x4,
|
||||
|
||||
// font oversampling
|
||||
FONT_NO_OVERSAMPLE = 0x0,
|
||||
FONT_OVERSAMPLE_X = 0x08,
|
||||
FONT_OVERSAMPLE_Y = 0x10,
|
||||
|
||||
// unicode ranges
|
||||
FONT_ASCII = 0x800, // Compatible charset
|
||||
FONT_AR = 0x001000, // Arabic and Arabic-Indic digits
|
||||
FONT_ZH = 0x002000, // Chinese Simplified (@todo: add ZH_FULL)
|
||||
FONT_EL = 0x004000, // Greek, Coptic, modern Georgian, Svan, Mingrelian, Ancient Greek
|
||||
FONT_EM = 0x008000, // Emoji
|
||||
FONT_EU = 0x010000, // Eastern/western Europe, IPA, Latin ext A/B
|
||||
FONT_HE = 0x020000, // Hebrew, Yiddish, Ladino, and other diaspora languages
|
||||
FONT_JP = 0x040000, // Hiragana, Katakana, Punctuations, Half-width chars
|
||||
FONT_KR = 0x080000, // Korean, Hangul
|
||||
FONT_RU = 0x100000, // Cyrillic + ext A/B
|
||||
FONT_TH = 0x200000, // Thai
|
||||
FONT_VI = 0x400000, // Vietnamese
|
||||
FONT_CJK = FONT_ZH|FONT_JP|FONT_KR,
|
||||
|
||||
// FONT_DEFAULTS = FONT_512 | FONT_NO_OVERSAMPLE | FONT_ASCII,
|
||||
};
|
||||
|
||||
// configures
|
||||
API void font_face(const char *face_tag, const char *filename_ttf, float font_size, unsigned flags);
|
||||
API void font_face_from_mem(const char *tag, const void *ttf_buffer, unsigned ttf_len, float font_size, unsigned flags);
|
||||
API void font_scales(const char *face_tag, float h1, float h2, float h3, float h4, float h5, float h6);
|
||||
API void font_color(const char *color_tag, uint32_t color);
|
||||
|
||||
// commands
|
||||
API vec2 font_xy();
|
||||
API void font_goto(float x, float y);
|
||||
API vec2 font_print(const char *text);
|
||||
API vec2 font_rect(const char *text);
|
||||
// void font_clip(vec2 topleft, vec2 bottomright);
|
||||
// void font_wrap(vec2 topleft, vec2 bottomright);
|
||||
|
||||
// syntax highlighting
|
||||
API void* font_colorize(const char *text, const char *comma_types, const char *comma_keywords); // comma separated tokens. expensive, please cache result.
|
||||
API vec2 font_highlight(const char *text, const void *colors);
|
||||
#line 0
|
||||
|
||||
#line 1 "engine/split/v4k_file.h"
|
||||
// -----------------------------------------------------------------------------
|
||||
// files, cache and virtual filesystem (registered directories and/or compressed zip archives).
|
||||
|
@ -2227,6 +2145,98 @@ API void ini_destroy(ini_t);
|
|||
API bool ini_write(const char *filename, const char *section, const char *key, const char *value);
|
||||
#line 0
|
||||
|
||||
#line 1 "engine/split/v4k_font.h"
|
||||
// -----------------------------------------------------------------------------
|
||||
// font framework
|
||||
// - rlyeh, public domain
|
||||
|
||||
// font size tags
|
||||
#define FONT_H1 "\1" // largest
|
||||
#define FONT_H2 "\2"
|
||||
#define FONT_H3 "\3"
|
||||
#define FONT_H4 "\4"
|
||||
#define FONT_H5 "\5"
|
||||
#define FONT_H6 "\6" // smallest
|
||||
|
||||
// font color tags
|
||||
#define FONT_COLOR1 "\x10"
|
||||
#define FONT_COLOR2 "\x11"
|
||||
#define FONT_COLOR3 "\x12"
|
||||
#define FONT_COLOR4 "\x13"
|
||||
#define FONT_COLOR5 "\x14"
|
||||
#define FONT_COLOR6 "\x15"
|
||||
#define FONT_COLOR7 "\x16"
|
||||
#define FONT_COLOR8 "\x17"
|
||||
#define FONT_COLOR9 "\x18"
|
||||
#define FONT_COLOR10 "\x19"
|
||||
|
||||
// font face tags
|
||||
#define FONT_FACE1 "\x1a"
|
||||
#define FONT_FACE2 "\x1b"
|
||||
#define FONT_FACE3 "\x1c"
|
||||
#define FONT_FACE4 "\x1d"
|
||||
#define FONT_FACE5 "\x1e"
|
||||
#define FONT_FACE6 "\x1f"
|
||||
|
||||
// font align tags
|
||||
#define FONT_LEFT "\\<"
|
||||
#define FONT_CENTER "\\|"
|
||||
#define FONT_RIGHT "\\>"
|
||||
#define FONT_TOP "\\^"
|
||||
#define FONT_MIDDLE "\\-"
|
||||
#define FONT_BASELINE "\\_"
|
||||
#define FONT_BOTTOM "\\v"
|
||||
|
||||
// font flags
|
||||
enum FONT_FLAGS {
|
||||
// font atlas size
|
||||
FONT_512 = 0x0,
|
||||
FONT_1024 = 0x1,
|
||||
FONT_2048 = 0x2,
|
||||
FONT_4096 = 0x4,
|
||||
|
||||
// font oversampling
|
||||
FONT_NO_OVERSAMPLE = 0x0,
|
||||
FONT_OVERSAMPLE_X = 0x08,
|
||||
FONT_OVERSAMPLE_Y = 0x10,
|
||||
|
||||
// unicode ranges
|
||||
FONT_ASCII = 0x800, // Compatible charset
|
||||
FONT_AR = 0x001000, // Arabic and Arabic-Indic digits
|
||||
FONT_ZH = 0x002000, // Chinese Simplified (@todo: add ZH_FULL)
|
||||
FONT_EL = 0x004000, // Greek, Coptic, modern Georgian, Svan, Mingrelian, Ancient Greek
|
||||
FONT_EM = 0x008000, // Emoji
|
||||
FONT_EU = 0x010000, // Eastern/western Europe, IPA, Latin ext A/B
|
||||
FONT_HE = 0x020000, // Hebrew, Yiddish, Ladino, and other diaspora languages
|
||||
FONT_JP = 0x040000, // Hiragana, Katakana, Punctuations, Half-width chars
|
||||
FONT_KR = 0x080000, // Korean, Hangul
|
||||
FONT_RU = 0x100000, // Cyrillic + ext A/B
|
||||
FONT_TH = 0x200000, // Thai
|
||||
FONT_VI = 0x400000, // Vietnamese
|
||||
FONT_CJK = FONT_ZH|FONT_JP|FONT_KR,
|
||||
|
||||
// FONT_DEFAULTS = FONT_512 | FONT_NO_OVERSAMPLE | FONT_ASCII,
|
||||
};
|
||||
|
||||
// configures
|
||||
API void font_face(const char *face_tag, const char *filename_ttf, float font_size, unsigned flags);
|
||||
API void font_face_from_mem(const char *tag, const void *ttf_buffer, unsigned ttf_len, float font_size, unsigned flags);
|
||||
API void font_scales(const char *face_tag, float h1, float h2, float h3, float h4, float h5, float h6);
|
||||
API void font_color(const char *color_tag, uint32_t color);
|
||||
|
||||
// commands
|
||||
API vec2 font_xy();
|
||||
API void font_goto(float x, float y);
|
||||
API vec2 font_print(const char *text);
|
||||
API vec2 font_rect(const char *text);
|
||||
// void font_clip(vec2 topleft, vec2 bottomright);
|
||||
// void font_wrap(vec2 topleft, vec2 bottomright);
|
||||
|
||||
// syntax highlighting
|
||||
API void* font_colorize(const char *text, const char *comma_types, const char *comma_keywords); // comma separated tokens. expensive, please cache result.
|
||||
API vec2 font_highlight(const char *text, const void *colors);
|
||||
#line 0
|
||||
|
||||
#line 1 "engine/split/v4k_input.h"
|
||||
// -----------------------------------------------------------------------------
|
||||
// input framework
|
||||
|
@ -2329,7 +2339,7 @@ enum INPUT_ENUMS {
|
|||
KEY_PAD1,KEY_PAD2,KEY_PAD3,KEY_PAD4,KEY_PAD5,KEY_PAD6,KEY_PAD7,KEY_PAD8,KEY_PAD9,KEY_PAD0, // beware: complicated on laptops
|
||||
KEY_PADADD,KEY_PADSUB,KEY_PADMUL,KEY_PADDIV,KEY_PADDOT,KEY_PADENTER, // beware: complicated on laptops
|
||||
|
||||
MOUSE_L, MOUSE_M, MOUSE_R,
|
||||
MOUSE_L, MOUSE_M, MOUSE_R, // @todo: MOUSE_CLICKS,
|
||||
GAMEPAD_CONNECTED, GAMEPAD_A, GAMEPAD_B, GAMEPAD_X, GAMEPAD_Y,
|
||||
GAMEPAD_UP, GAMEPAD_DOWN, GAMEPAD_LEFT, GAMEPAD_RIGHT, GAMEPAD_MENU, GAMEPAD_START,
|
||||
GAMEPAD_LB, GAMEPAD_RB, GAMEPAD_LTHUMB, GAMEPAD_RTHUMB,
|
||||
|
@ -3192,6 +3202,7 @@ enum TEXTURE_FLAGS {
|
|||
// @fixme
|
||||
TEXTURE_SRGB = 1 << 24,
|
||||
TEXTURE_BGR = 1 << 25,
|
||||
TEXTURE_BGRA = TEXTURE_BGR,
|
||||
TEXTURE_ARRAY = 1 << 26,
|
||||
};
|
||||
|
||||
|
@ -3986,6 +3997,7 @@ API light_t* scene_index_light(unsigned index);
|
|||
API char* tempvl(const char *fmt, va_list);
|
||||
API char* tempva(const char *fmt, ...);
|
||||
#define va(...) (((&printf) || printf(__VA_ARGS__), tempva(__VA_ARGS__))) // vs2015 check trick
|
||||
#define vac (const char*)va
|
||||
|
||||
// string: allocated api (heap). FREE() after use
|
||||
API char* strcatf(char **s, const char *buf);
|
||||
|
@ -4072,6 +4084,93 @@ API unsigned quark_intern( quarks_db*, const char *string );
|
|||
API const char *quark_string( quarks_db*, unsigned key );
|
||||
#line 0
|
||||
|
||||
#line 1 "engine/split/v4k_system.h"
|
||||
// -----------------------------------------------------------------------------
|
||||
// system framework utils
|
||||
// - rlyeh, public domain.
|
||||
//
|
||||
// Note: Windows users add `/Zi` compilation flags, else add `-g` and/or `-ldl` flags
|
||||
// Note: If you are linking your binary using GNU ld you need to add --export-dynamic
|
||||
|
||||
API void* thread( int (*thread_func)(void* user_data), void* user_data );
|
||||
API void thread_destroy( void *thd );
|
||||
|
||||
API int argc();
|
||||
API char* argv(int);
|
||||
|
||||
API int flag(const char *commalist); // --arg // app_flag?
|
||||
API const char* option(const char *commalist, const char *defaults); // --arg=string or --arg string
|
||||
API int optioni(const char *commalist, int defaults); // --arg=integer or --arg integer // argvi() ?
|
||||
API float optionf(const char *commalist, float defaults); // --arg=float or --arg float // flagf() ?
|
||||
|
||||
API void tty_attach();
|
||||
API void tty_detach();
|
||||
API void tty_color(unsigned color);
|
||||
API void tty_reset();
|
||||
|
||||
API const char* app_exec(const char *command); // returns ("%15d %s", retcode, output_last_line)
|
||||
API int app_spawn(const char *command);
|
||||
API int app_cores();
|
||||
API int app_battery(); /// returns battery level [1..100]. also positive if charging (+), negative if discharging (-), and 0 if no battery is present.
|
||||
|
||||
API const char* app_name();
|
||||
API const char* app_path();
|
||||
API const char* app_cache();
|
||||
API const char* app_temp();
|
||||
API const char* app_cmdline();
|
||||
|
||||
API void app_beep();
|
||||
API void app_hang();
|
||||
API void app_crash();
|
||||
API void app_singleton(const char *guid);
|
||||
API bool app_open(const char *folder_file_or_url);
|
||||
|
||||
API const char* app_loadfile();
|
||||
API const char* app_savefile();
|
||||
|
||||
|
||||
API char* callstack( int traces ); // write callstack into a temporary string. <0 traces to invert order. do not free().
|
||||
API int callstackf( FILE *fp, int traces ); // write callstack to file. <0 traces to invert order.
|
||||
|
||||
API void die(const char *message);
|
||||
API void alert(const char *message);
|
||||
API void hexdump( const void *ptr, unsigned len );
|
||||
API void hexdumpf( FILE *fp, const void *ptr, unsigned len, int width );
|
||||
API void breakpoint();
|
||||
API bool has_debugger();
|
||||
|
||||
API void trap_install(void);
|
||||
API const char *trap_name(int signal); // helper util
|
||||
API void trap_on_ignore(int signal); // helper util
|
||||
API void trap_on_quit(int signal); // helper util
|
||||
API void trap_on_abort(int signal); // helper util
|
||||
API void trap_on_debug(int signal); // helper util
|
||||
|
||||
#define PANIC(...) PANIC(va(__VA_ARGS__), strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__, __LINE__) // die() ?
|
||||
API int (PANIC)(const char *error, const char *file, int line);
|
||||
|
||||
#define PRINTF(...) PRINTF(va(__VA_ARGS__), 1[#__VA_ARGS__] == '!' ? callstack(+48) : "", strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__, __LINE__, __FUNCTION__)
|
||||
API int (PRINTF)(const char *text, const char *stack, const char *file, int line, const char *function);
|
||||
|
||||
#define test(expr) test(strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__,__LINE__,#expr,!!(expr))
|
||||
API int (test)(const char *file, int line, const char *expr, bool result);
|
||||
|
||||
#if ENABLE_AUTOTESTS
|
||||
#define AUTOTEST AUTORUN
|
||||
#else
|
||||
#define AUTOTEST static void concat(concat(concat(disabled_test_, __LINE__), _), __COUNTER__)()
|
||||
#endif
|
||||
|
||||
// AUTOTEST { test(1<2); }
|
||||
|
||||
#if ENABLE_RETAIL
|
||||
#undef PRINTF
|
||||
#define PRINTF(...) 0
|
||||
#undef test
|
||||
#define test(expr) 0
|
||||
#endif
|
||||
#line 0
|
||||
|
||||
#line 1 "engine/split/v4k_time.h"
|
||||
// -----------------------------------------------------------------------------
|
||||
// time framework utils
|
||||
|
@ -4226,98 +4325,11 @@ typedef struct curve_t {
|
|||
|
||||
API curve_t curve();
|
||||
API void curve_add(curve_t *c, vec3 p);
|
||||
API void curve_finish(curve_t *c, int num_points);
|
||||
API void curve_end(curve_t *c, int num_points);
|
||||
API vec3 curve_eval(curve_t *c, float dt, unsigned *color);
|
||||
API void curve_destroy(curve_t *c);
|
||||
#line 0
|
||||
|
||||
#line 1 "engine/split/v4k_system.h"
|
||||
// -----------------------------------------------------------------------------
|
||||
// system framework utils
|
||||
// - rlyeh, public domain.
|
||||
//
|
||||
// Note: Windows users add `/Zi` compilation flags, else add `-g` and/or `-ldl` flags
|
||||
// Note: If you are linking your binary using GNU ld you need to add --export-dynamic
|
||||
|
||||
API void* thread( int (*thread_func)(void* user_data), void* user_data );
|
||||
API void thread_destroy( void *thd );
|
||||
|
||||
API int argc();
|
||||
API char* argv(int);
|
||||
|
||||
API int flag(const char *commalist); // --arg // app_flag?
|
||||
API const char* option(const char *commalist, const char *defaults); // --arg=string or --arg string
|
||||
API int optioni(const char *commalist, int defaults); // --arg=integer or --arg integer // argvi() ?
|
||||
API float optionf(const char *commalist, float defaults); // --arg=float or --arg float // flagf() ?
|
||||
|
||||
API void tty_attach();
|
||||
API void tty_detach();
|
||||
API void tty_color(unsigned color);
|
||||
API void tty_reset();
|
||||
|
||||
API const char* app_exec(const char *command); // returns ("%15d %s", retcode, output_last_line)
|
||||
API int app_spawn(const char *command);
|
||||
API int app_cores();
|
||||
API int app_battery(); /// returns battery level [1..100]. also positive if charging (+), negative if discharging (-), and 0 if no battery is present.
|
||||
|
||||
API const char* app_name();
|
||||
API const char* app_path();
|
||||
API const char* app_cache();
|
||||
API const char* app_temp();
|
||||
API const char* app_cmdline();
|
||||
|
||||
API void app_beep();
|
||||
API void app_hang();
|
||||
API void app_crash();
|
||||
API void app_singleton(const char *guid);
|
||||
API bool app_open(const char *folder_file_or_url);
|
||||
|
||||
API const char* app_loadfile();
|
||||
API const char* app_savefile();
|
||||
|
||||
|
||||
API char* callstack( int traces ); // write callstack into a temporary string. <0 traces to invert order. do not free().
|
||||
API int callstackf( FILE *fp, int traces ); // write callstack to file. <0 traces to invert order.
|
||||
|
||||
API void die(const char *message);
|
||||
API void alert(const char *message);
|
||||
API void hexdump( const void *ptr, unsigned len );
|
||||
API void hexdumpf( FILE *fp, const void *ptr, unsigned len, int width );
|
||||
API void breakpoint();
|
||||
API bool has_debugger();
|
||||
|
||||
API void trap_install(void);
|
||||
API const char *trap_name(int signal); // helper util
|
||||
API void trap_on_ignore(int signal); // helper util
|
||||
API void trap_on_quit(int signal); // helper util
|
||||
API void trap_on_abort(int signal); // helper util
|
||||
API void trap_on_debug(int signal); // helper util
|
||||
|
||||
#define PANIC(...) PANIC(va(__VA_ARGS__), strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__, __LINE__) // die() ?
|
||||
API int (PANIC)(const char *error, const char *file, int line);
|
||||
|
||||
#define PRINTF(...) PRINTF(va(__VA_ARGS__), 1[#__VA_ARGS__] == '!' ? callstack(+48) : "", strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__, __LINE__, __FUNCTION__)
|
||||
API int (PRINTF)(const char *text, const char *stack, const char *file, int line, const char *function);
|
||||
|
||||
#define test(expr) test(strrchr(__FILE__, '/')?(strrchr(__FILE__, '/')+2):__FILE__,__LINE__,#expr,!!(expr))
|
||||
API int (test)(const char *file, int line, const char *expr, bool result);
|
||||
|
||||
#if ENABLE_AUTOTESTS
|
||||
#define AUTOTEST AUTORUN
|
||||
#else
|
||||
#define AUTOTEST static void concat(concat(concat(disabled_test_, __LINE__), _), __COUNTER__)()
|
||||
#endif
|
||||
|
||||
// AUTOTEST { test(1<2); }
|
||||
|
||||
#if ENABLE_RETAIL
|
||||
#undef PRINTF
|
||||
#define PRINTF(...) 0
|
||||
#undef test
|
||||
#define test(expr) 0
|
||||
#endif
|
||||
#line 0
|
||||
|
||||
#line 1 "engine/split/v4k_ui.h"
|
||||
// -----------------------------------------------------------------------------
|
||||
// immediate ui framework
|
||||
|
@ -4541,14 +4553,17 @@ enum CURSOR_SHAPES {
|
|||
CURSOR_NONE,
|
||||
CURSOR_HW_ARROW, // default
|
||||
CURSOR_HW_IBEAM, // i-beam text cursor
|
||||
CURSOR_HW_CROSS, // crosshair
|
||||
CURSOR_HW_HAND, // hand, clickable
|
||||
CURSOR_HW_HDRAG, // horizontal drag/resize
|
||||
CURSOR_HW_VDRAG, // vertical drag/resize
|
||||
CURSOR_HW_HAND, // hand, clickable
|
||||
CURSOR_HW_CROSS, // crosshair
|
||||
CURSOR_SW_AUTO, // software cursor, ui driven. note: this is the only icon that may be recorded or snapshotted
|
||||
};
|
||||
|
||||
API void window_cursor_shape(unsigned shape);
|
||||
|
||||
API const char *window_clipboard();
|
||||
API void window_setclipboard(const char *text);
|
||||
#line 0
|
||||
|
||||
// ----
|
||||
|
@ -4565,7 +4580,7 @@ API void window_cursor_shape(unsigned shape);
|
|||
#include <emscripten/html5.h>
|
||||
#define gladLoadGL(func) (glewExperimental = true, glewInit() == GLEW_OK)
|
||||
#else
|
||||
#if is(win32) /*&& is(tcc)*/ // && WITH_DLL
|
||||
#if is(win32) /*&& is(tcc)*/ // && ENABLE_DLL
|
||||
#ifdef GLAD_API_CALL
|
||||
#undef GLAD_API_CALL
|
||||
#endif
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<paths>
|
||||
<left>C:\Projects\v4k\_fwk\</left>
|
||||
<right>C:\Projects\fwk-mirror\</right>
|
||||
<filter>*.cpp,*.h,*.c</filter>
|
||||
<filter>*.cpp,*.h,*.c,*.inl,*.lua</filter>
|
||||
<subfolders>1</subfolders>
|
||||
<left-readonly>0</left-readonly>
|
||||
<right-readonly>1</right-readonly>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash 2>nul || goto :windows
|
||||
|
||||
# linux + osx -----------------------------------------------------------------
|
||||
cd `dirname $0`
|
||||
|
||||
git clone https://github.com/assimp/assimp && cd assimp && git checkout 05115b07
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DASSIMP_BUILD_TESTS=OFF .
|
||||
make -j 8
|
||||
cp bin/libassimp.so ../libassimp.so
|
||||
cp bin/libassimp.so ../libassimp.so.5
|
||||
cp bin/libassimp.so ../libassimp.so.5.1.4
|
||||
cp bin/libassimp.dylib ../libassimp.dylib
|
||||
cp bin/libassimp.dylib ../libassimp.5.dylib
|
||||
cp bin/libassimp.dylib ../libassimp.5.0.1.dylib
|
||||
cd ..
|
||||
|
||||
cc ass2iqe.c -o ass2iqe.linux -O2 -I assimp/include/ libassimp.so -lm
|
||||
cc ass2iqe.c -o ass2iqe.osx -O2 -I assimp/include/ libassimp.dylib -lm
|
||||
|
||||
exit
|
||||
|
||||
|
||||
:windows -----------------------------------------------------------------------
|
||||
@echo off
|
||||
cd "%~dp0"
|
||||
|
||||
git clone https://github.com/assimp/assimp && md assimp\.build && pushd assimp\.build && git checkout 05115b07
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release && (make || msbuild assimp.sln -m -p:Configuration=Release)
|
||||
popd
|
||||
|
||||
xcopy /y assimp\.build\bin\release\*.dll
|
||||
xcopy /y assimp\.build\lib\release\*.lib
|
||||
|
||||
copy /y assimp\include\assimp\config.h.in assimp\config.h && fart -- assimp\config.h "cmakedefine" "//#define"
|
||||
cl ass2iqe.c -I . -I assimp\include assimp-vc14?-mt.lib /O2 /Oy /MT /DNDEBUG
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash 2>nul || goto :windows
|
||||
|
||||
# linux + osx -----------------------------------------------------------------
|
||||
cd `dirname $0`
|
||||
|
||||
git clone https://github.com/septag/glslcc && mkdir glslcc/.build
|
||||
cd glslcc/.build
|
||||
cmake .. -B . -DCMAKE_BUILD_TYPE=Release
|
||||
make -j 8
|
||||
# cp -f glslcc/.build/src/Release/glslcc.exe .
|
||||
cd ".."
|
||||
|
||||
exit
|
||||
|
||||
|
||||
:windows -----------------------------------------------------------------------
|
||||
@echo off
|
||||
cd "%~dp0"
|
||||
|
||||
git clone https://github.com/septag/glslcc && md glslcc\.build && pushd glslcc\.build
|
||||
cmake .. -B . -DCMAKE_BUILD_TYPE=Release && (make || msbuild glslcc.sln -m -p:Configuration=Release)
|
||||
popd
|
||||
|
||||
xcopy /y glslcc\.build\src\Release\glslcc.exe
|
|
@ -1,4 +0,0 @@
|
|||
return {
|
||||
"_preload.lua",
|
||||
"ninja.lua",
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
--
|
||||
-- Name: premake-ninja/_preload.lua
|
||||
-- Purpose: Define the ninja action.
|
||||
-- Author: Dmitry Ivanov
|
||||
-- Created: 2015/07/04
|
||||
-- Copyright: (c) 2015 Dmitry Ivanov
|
||||
--
|
||||
|
||||
local p = premake
|
||||
|
||||
newaction
|
||||
{
|
||||
-- Metadata for the command line and help system
|
||||
trigger = "ninja",
|
||||
shortname = "ninja",
|
||||
description = "Ninja is a small build system with a focus on speed",
|
||||
|
||||
-- The capabilities of this action
|
||||
valid_kinds = {"ConsoleApp", "WindowedApp", "SharedLib", "StaticLib", "None"}, -- Not supported: Makefile, Packaging, SharedItems, Utility
|
||||
valid_languages = {"C", "C++"},
|
||||
valid_tools = {cc = { "gcc", "clang", "msc" }},
|
||||
|
||||
toolset = iif(os.target() == "windows", "msc-v142", -- Visual Studio 2019
|
||||
iif(os.target() == "macosx", "clang",
|
||||
"gcc")),
|
||||
|
||||
-- Workspace and project generation logic
|
||||
onWorkspace = function(wks)
|
||||
p.eol("\r\n")
|
||||
p.indent(" ")
|
||||
p.generate(wks, "build.ninja", p.modules.ninja.generateWorkspace)
|
||||
end,
|
||||
onProject = function(prj)
|
||||
p.eol("\r\n")
|
||||
p.indent(" ")
|
||||
p.modules.ninja.generateProject(prj)
|
||||
end,
|
||||
onBranch = function(prj)
|
||||
p.eol("\r\n")
|
||||
p.indent(" ")
|
||||
p.modules.ninja.generateProject(prj)
|
||||
end,
|
||||
onCleanSolution = function(sln)
|
||||
-- TODO
|
||||
end,
|
||||
onCleanProject = function(prj)
|
||||
-- TODO
|
||||
end,
|
||||
onCleanTarget = function(prj)
|
||||
-- TODO
|
||||
end,
|
||||
}
|
||||
|
||||
|
||||
--
|
||||
-- Decide when the full module should be loaded.
|
||||
--
|
||||
|
||||
return function(cfg)
|
||||
return (_ACTION == "ninja")
|
||||
end
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,20 +0,0 @@
|
|||
#include "v4k.h"
|
||||
|
||||
int main() {
|
||||
if (argc() < 3) {
|
||||
printf("%s [color] [trans]\n", argv(0));
|
||||
return 1;
|
||||
}
|
||||
|
||||
FILE *f = fopen(argv(1), "rb");
|
||||
if (!f) {
|
||||
printf("File not found.\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
//stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
|
||||
int x, y, bpp;
|
||||
char *buf = stbi_load_from_file()
|
||||
|
||||
return 0;
|
||||
}
|
10
tools/cook.c
10
tools/cook.c
|
@ -1,7 +1,7 @@
|
|||
#define V4K_IMPLEMENTATION
|
||||
#define COOK_ON_DEMAND 0
|
||||
#define COOK_FROM_TERMINAL 1
|
||||
#include "../engine/joint/v4k.h"
|
||||
#include "joint/v4k.h"
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
double timer = time_ss();
|
||||
|
@ -30,7 +30,7 @@ int main(int argc, const char **argv) {
|
|||
}
|
||||
|
||||
// compiled with:
|
||||
// tcc cook.c
|
||||
// cl cook.c /openmp /Os /Ox /O2 /Oy /MT /DNDEBUG /GL /GF /Gw /arch:AVX2 /link /OPT:ICF /LTCG
|
||||
// cc -ObjC cook.c -o cook.osx -framework Cocoa -framework IOKit -framework audiotoolbox -O3
|
||||
// cc cook.c -o cook.linux -lm -lpthread -ldl -lX11 -O3
|
||||
// tcc cook.c -I..\engine
|
||||
// cl cook.c -I..\engine /openmp /Os /Ox /O2 /Oy /MT /DNDEBUG /GL /GF /Gw /arch:AVX2 /link /OPT:ICF /LTCG
|
||||
// cc -ObjC cook.c -I../engine -o cook.osx -framework Cocoa -framework IOKit -framework audiotoolbox -O3
|
||||
// cc cook.c -I../engine -o cook.linux -lm -lpthread -ldl -lX11 -O3
|
||||
|
|
BIN
tools/cook.exe
BIN
tools/cook.exe
Binary file not shown.
BIN
tools/cook.linux
BIN
tools/cook.linux
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,264 @@
|
|||
// lite editor, platform details
|
||||
// - rlyeh, public domain
|
||||
|
||||
#define LT_DATAPATH "/lite"
|
||||
|
||||
#define lt_assert(x) ASSERT(x)
|
||||
|
||||
#define lt_realpath(p, q) file_pathabs(p)
|
||||
#define lt_realpath_free(p)
|
||||
|
||||
#define lt_malloc(n) MALLOC(n)
|
||||
#define lt_calloc(n,m) CALLOC(n,m)
|
||||
#define lt_free(p) FREE(p)
|
||||
#define lt_memcpy(d,s,c) memcpy(d,s,c)
|
||||
#define lt_memset(p,ch,c) memset(p,ch,c)
|
||||
|
||||
#define lt_time_ms() time_ms()
|
||||
#define lt_sleep_ms(ms) sleep_ms(ms)
|
||||
|
||||
#define lt_getclipboard(w) window_clipboard()
|
||||
#define lt_setclipboard(w,s) window_setclipboard(s)
|
||||
|
||||
#define lt_window() window_handle()
|
||||
#define lt_setwindowmode(m) window_fullscreen(m == 2), (m < 2 && (window_maximize(m),1)) // 0:normal,1:maximized,2:fullscreen
|
||||
#define lt_setwindowtitle(t) //window_title(t)
|
||||
#define lt_haswindowfocus() window_has_focus()
|
||||
#define lt_setcursor(shape) window_cursor_shape(lt_events & (1<<31) ? CURSOR_SW_AUTO : shape+1) // 0:arrow,1:ibeam,2:sizeh,3:sizev,4:hand
|
||||
|
||||
#define lt_prompt(msg,title) ifndef(win32, 0, (MessageBoxA(0, msg, title, MB_YESNO | MB_ICONWARNING) == IDYES))
|
||||
|
||||
unsigned lt_events = ~0u;
|
||||
int lt_mx = 0, lt_my = 0, lt_wx = 0, lt_wy = 0, lt_ww = 0, lt_wh = 0;
|
||||
|
||||
typedef struct lt_surface {
|
||||
unsigned w, h;
|
||||
void *pixels;
|
||||
texture_t t;
|
||||
} lt_surface;
|
||||
|
||||
typedef struct lt_rect {
|
||||
int x, y, width, height;
|
||||
} lt_rect;
|
||||
|
||||
lt_surface *lt_getsurface(void *window) {
|
||||
static lt_surface s = {0};
|
||||
return &s;
|
||||
}
|
||||
void lt_updatesurfacerects(lt_surface *s, lt_rect* rects, unsigned count) {
|
||||
// update contents
|
||||
texture_update(&s->t, s->w, s->h, 4, s->pixels, TEXTURE_LINEAR|TEXTURE_BGRA);
|
||||
}
|
||||
|
||||
void ren_set_clip_rect(struct lt_rect rect);
|
||||
void rencache_invalidate(void);
|
||||
int lt_resizesurface(lt_surface *s, int ww, int wh) {
|
||||
s->w = ww, s->h = wh;
|
||||
if( s->t.id == 0 || s->w != s->t.w || s->h != s->t.h ) {
|
||||
// invalidate tiles
|
||||
ren_set_clip_rect( (lt_rect) { 0, 0, s->w, s->h } );
|
||||
rencache_invalidate();
|
||||
|
||||
// texture clear
|
||||
if( !s->t.id ) s->t = texture_create(1, 1, 4, " ", TEXTURE_LINEAR|TEXTURE_RGBA|TEXTURE_BYTE );
|
||||
s->pixels = REALLOC(s->pixels, s->w * s->h * 4);
|
||||
memset(s->pixels, 0, s->w * s->h * 4);
|
||||
|
||||
// texture update
|
||||
lt_updatesurfacerects(s,0,0);
|
||||
return 1; // resized
|
||||
}
|
||||
return 0; // unchanged
|
||||
}
|
||||
|
||||
void *lt_load_file(const char *filename, int *size) {
|
||||
int datalen; char *data = file_load(filename, &datalen);
|
||||
if( !data || !datalen ) {
|
||||
filename = (const char *)file_normalize(filename);
|
||||
if( strbegi(filename, app_path()) ) filename += strlen(app_path());
|
||||
data = vfs_load(filename, &datalen);
|
||||
}
|
||||
if (size) *size = 0;
|
||||
if (!data) { return NULL; }
|
||||
if (size) *size = datalen;
|
||||
// return permanent buffers here, as file_load() and vfs_load() do return temporaries
|
||||
data = memcpy(MALLOC(datalen+1), data, datalen);
|
||||
data[datalen] = 0;
|
||||
return data;
|
||||
}
|
||||
|
||||
const char* lt_button_name(int button) {
|
||||
if(button == GLFW_MOUSE_BUTTON_1) return "left";
|
||||
if(button == GLFW_MOUSE_BUTTON_2) return "middle";
|
||||
if(button == GLFW_MOUSE_BUTTON_3) return "right";
|
||||
return "?";
|
||||
}
|
||||
|
||||
char* lt_key_name(char *dst, int key, int vk, int mods) {
|
||||
// @todo: ALTGR -> left ctrl + right alt
|
||||
|
||||
if( key == GLFW_KEY_UP ) return "up";
|
||||
if( key == GLFW_KEY_DOWN ) return "down";
|
||||
if( key == GLFW_KEY_LEFT ) return "left";
|
||||
if( key == GLFW_KEY_RIGHT ) return "right";
|
||||
if( key == GLFW_KEY_LEFT_ALT ) return "left alt";
|
||||
if( key == GLFW_KEY_RIGHT_ALT ) return "right alt";
|
||||
if( key == GLFW_KEY_LEFT_SHIFT ) return "left shift";
|
||||
if( key == GLFW_KEY_RIGHT_SHIFT ) return "right shift";
|
||||
if( key == GLFW_KEY_LEFT_CONTROL ) return "left ctrl";
|
||||
if( key == GLFW_KEY_RIGHT_CONTROL ) return "right ctrl";
|
||||
if( key == GLFW_KEY_LEFT_SUPER ) return "left windows";
|
||||
if( key == GLFW_KEY_RIGHT_SUPER ) return "left windows";
|
||||
|
||||
if( key == GLFW_KEY_ESCAPE ) return "escape";
|
||||
if( key == GLFW_KEY_BACKSPACE ) return "backspace";
|
||||
if( key == GLFW_KEY_ENTER ) return "return";
|
||||
if( key == GLFW_KEY_KP_ENTER ) return "keypad enter";
|
||||
if( key == GLFW_KEY_TAB ) return "tab";
|
||||
if( key == GLFW_KEY_CAPS_LOCK ) return "capslock";
|
||||
|
||||
if( key == GLFW_KEY_HOME ) return "home";
|
||||
if( key == GLFW_KEY_END ) return "end";
|
||||
if( key == GLFW_KEY_INSERT ) return "insert";
|
||||
if( key == GLFW_KEY_DELETE ) return "delete";
|
||||
if( key == GLFW_KEY_PAGE_UP ) return "pageup";
|
||||
if( key == GLFW_KEY_PAGE_DOWN ) return "pagedown";
|
||||
|
||||
if( key == GLFW_KEY_F1 ) return "f1";
|
||||
if( key == GLFW_KEY_F2 ) return "f2";
|
||||
if( key == GLFW_KEY_F3 ) return "f3";
|
||||
if( key == GLFW_KEY_F4 ) return "f4";
|
||||
if( key == GLFW_KEY_F5 ) return "f5";
|
||||
if( key == GLFW_KEY_F6 ) return "f6";
|
||||
if( key == GLFW_KEY_F7 ) return "f7";
|
||||
if( key == GLFW_KEY_F8 ) return "f8";
|
||||
if( key == GLFW_KEY_F9 ) return "f9";
|
||||
if( key == GLFW_KEY_F10 ) return "f10";
|
||||
if( key == GLFW_KEY_F11 ) return "f11";
|
||||
if( key == GLFW_KEY_F12 ) return "f12";
|
||||
|
||||
const char *name = glfwGetKeyName(key, vk);
|
||||
strcpy(dst, name ? name : "");
|
||||
char *p = dst;
|
||||
while (*p) {
|
||||
*p = tolower(*p);
|
||||
p++;
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
void lt_globpath(struct lua_State*L, const char *path) {
|
||||
unsigned j = 0;
|
||||
|
||||
if(!strend(path, "/")) path = (const char *)va("%s/", path);
|
||||
for( dir *d = dir_open(path, ""); d; dir_close(d), d = 0 ) {
|
||||
for( unsigned i = 0, end = dir_count(d); i < end; ++i ) {
|
||||
char *name = dir_name(d,i);
|
||||
char *last = name + strlen(name) - 1;
|
||||
if( *last == '/' ) *last = '\0';
|
||||
name = file_name(name);
|
||||
lua_pushstring(L, name);
|
||||
lua_rawseti(L, -2, ++j);
|
||||
}
|
||||
}
|
||||
|
||||
for( const char *section = strstri(path, LT_DATAPATH); section && section[sizeof(LT_DATAPATH)-1] == '/'; section = 0) {
|
||||
array(char*) list = vfs_list("**");
|
||||
for( unsigned i = 0, end = array_count(list); i < end; ++i ) {
|
||||
char *name = list[i];
|
||||
if( !strstri(name, section+1) ) continue;
|
||||
lua_pushstring(L, file_name(name));
|
||||
lua_rawseti(L, -2, ++j);
|
||||
}
|
||||
if( array_count(list) ) return;
|
||||
}
|
||||
}
|
||||
|
||||
int lt_emit_event(lua_State *L, const char *event_name, const char *event_fmt, ...) {
|
||||
int count = 0;
|
||||
lua_pushstring(L, event_name);
|
||||
if( event_fmt ) {
|
||||
va_list va;
|
||||
va_start(va, event_fmt);
|
||||
for( ; event_fmt[count]; ++count ) {
|
||||
/**/ if( event_fmt[count] == 'd' ) { int d = va_arg(va, int); lua_pushnumber(L, d); }
|
||||
else if( event_fmt[count] == 'f' ) { double f = va_arg(va, double); lua_pushnumber(L, f); }
|
||||
else if( event_fmt[count] == 's' ) { const char *s = va_arg(va, const char *); lua_pushstring(L, s); }
|
||||
}
|
||||
va_end(va);
|
||||
}
|
||||
return 1+count;
|
||||
}
|
||||
|
||||
int printi(int i) {
|
||||
// printf("clicks: %d\n", i);
|
||||
return i;
|
||||
}
|
||||
|
||||
static const char* codepoint_to_utf8(unsigned c);
|
||||
int lt_poll_event(lua_State *L) { // init.lua > core.step() wakes on mousemoved || inputtext
|
||||
int rc = 0;
|
||||
char buf[16];
|
||||
static int prevx = 0, prevy = 0;
|
||||
|
||||
static unsigned clicks_time = 0, clicks = 0;
|
||||
if( (lt_time_ms() - clicks_time) > 400 ) clicks = 0;
|
||||
|
||||
//
|
||||
|
||||
for( GLEQevent e; gleqNextEvent(&e); gleqFreeEvent(&e) )
|
||||
if( lt_events & e.type )
|
||||
switch (e.type) {
|
||||
default:
|
||||
break; case GLEQ_WINDOW_CLOSED: // it used to be ok. depends on window_swap() flow
|
||||
rc += lt_emit_event(L, "quit", NULL);
|
||||
return rc;
|
||||
|
||||
break; case GLEQ_WINDOW_MOVED:
|
||||
lt_wx = e.pos.x;
|
||||
lt_wy = e.pos.y;
|
||||
|
||||
break; case GLEQ_WINDOW_RESIZED:
|
||||
rc += lt_emit_event(L, "resized", "dd", lt_ww = e.size.width, lt_wh = e.size.height);
|
||||
lt_resizesurface(lt_getsurface(lt_window()), lt_ww, lt_wh);
|
||||
|
||||
break; case GLEQ_WINDOW_REFRESH:
|
||||
rc += lt_emit_event(L, "exposed", NULL);
|
||||
rencache_invalidate();
|
||||
|
||||
break; case GLEQ_FILE_DROPPED:
|
||||
rc += lt_emit_event(L, "filedropped", "sdd", e.file.paths[0], lt_mx, lt_my);
|
||||
|
||||
break; case GLEQ_KEY_PRESSED:
|
||||
case GLEQ_KEY_REPEATED:
|
||||
rc += lt_emit_event(L, "keypressed", "s", lt_key_name(buf, e.keyboard.key, e.keyboard.scancode, e.keyboard.mods));
|
||||
goto bottom;
|
||||
|
||||
break; case GLEQ_KEY_RELEASED:
|
||||
rc += lt_emit_event(L, "keyreleased", "s", lt_key_name(buf, e.keyboard.key, e.keyboard.scancode, e.keyboard.mods));
|
||||
goto bottom;
|
||||
|
||||
break; case GLEQ_CODEPOINT_INPUT:
|
||||
rc += lt_emit_event(L, "textinput", "s", codepoint_to_utf8(e.codepoint));
|
||||
|
||||
break; case GLEQ_BUTTON_PRESSED:
|
||||
rc += lt_emit_event(L, "mousepressed", "sddd", lt_button_name(e.mouse.button), lt_mx, lt_my, printi(1 + clicks));
|
||||
|
||||
break; case GLEQ_BUTTON_RELEASED:
|
||||
clicks += e.mouse.button == GLFW_MOUSE_BUTTON_1;
|
||||
clicks_time = lt_time_ms();
|
||||
rc += lt_emit_event(L, "mousereleased", "sdd", lt_button_name(e.mouse.button), lt_mx, lt_my);
|
||||
|
||||
break; case GLEQ_CURSOR_MOVED:
|
||||
lt_mx = e.pos.x - lt_wx, lt_my = e.pos.y - lt_wy;
|
||||
rc += lt_emit_event(L, "mousemoved", "dddd", lt_mx, lt_my, lt_mx - prevx, lt_my - prevy);
|
||||
prevx = lt_mx, prevy = lt_my;
|
||||
|
||||
break; case GLEQ_SCROLLED:
|
||||
rc += lt_emit_event(L, "mousewheel", "f", e.scroll.y);
|
||||
}
|
||||
|
||||
bottom:;
|
||||
|
||||
return rc;
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
/*
|
||||
* GLEQ - A basic event queue for GLFW 3
|
||||
* Copyright © Camilla Löwy <elmindreda@glfw.org>
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would
|
||||
* be appreciated but is not required.
|
||||
*
|
||||
* 2. Altered source versions must be plainly marked as such, and must not
|
||||
* be misrepresented as being the original software.
|
||||
*
|
||||
* 3. This notice may not be removed or altered from any source
|
||||
* distribution.
|
||||
*/
|
||||
|
||||
#ifndef GLEQ_HEADER_FILE
|
||||
#define GLEQ_HEADER_FILE
|
||||
|
||||
// #include <GLFW/glfw3.h>
|
||||
|
||||
#ifdef GLEQ_STATIC
|
||||
#define GLEQDEF static
|
||||
#else
|
||||
#define GLEQDEF extern
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GLEQ_NONE = 0,
|
||||
GLEQ_WINDOW_MOVED = 1<<1,
|
||||
GLEQ_WINDOW_RESIZED = 1<<2,
|
||||
GLEQ_WINDOW_CLOSED = 1<<3,
|
||||
GLEQ_WINDOW_REFRESH = 1<<4,
|
||||
GLEQ_WINDOW_FOCUSED = 1<<5,
|
||||
GLEQ_WINDOW_DEFOCUSED = 1<<6,
|
||||
GLEQ_WINDOW_ICONIFIED = 1<<7,
|
||||
GLEQ_WINDOW_UNICONIFIED = 1<<8,
|
||||
GLEQ_FRAMEBUFFER_RESIZED = 1<<9,
|
||||
GLEQ_BUTTON_PRESSED = 1<<10,
|
||||
GLEQ_BUTTON_RELEASED = 1<<11,
|
||||
GLEQ_CURSOR_MOVED = 1<<12,
|
||||
GLEQ_CURSOR_ENTERED = 1<<13,
|
||||
GLEQ_CURSOR_LEFT = 1<<14,
|
||||
GLEQ_SCROLLED = 1<<15,
|
||||
GLEQ_KEY_PRESSED = 1<<16,
|
||||
GLEQ_KEY_REPEATED = 1<<17,
|
||||
GLEQ_KEY_RELEASED = 1<<18,
|
||||
GLEQ_CODEPOINT_INPUT = 1<<19,
|
||||
GLEQ_MONITOR_CONNECTED = 1<<20,
|
||||
GLEQ_MONITOR_DISCONNECTED = 1<<21,
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
GLEQ_FILE_DROPPED = 1<<22,
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 2
|
||||
GLEQ_JOYSTICK_CONNECTED = 1<<23,
|
||||
GLEQ_JOYSTICK_DISCONNECTED = 1<<24,
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
GLEQ_WINDOW_MAXIMIZED = 1<<25,
|
||||
GLEQ_WINDOW_UNMAXIMIZED = 1<<26,
|
||||
GLEQ_WINDOW_SCALE_CHANGED = 1<<27,
|
||||
#endif
|
||||
} GLEQtype;
|
||||
|
||||
typedef struct GLEQevent
|
||||
{
|
||||
unsigned/*GLEQtype*/ type;
|
||||
union {
|
||||
GLFWwindow* window;
|
||||
GLFWmonitor* monitor;
|
||||
int joystick;
|
||||
};
|
||||
union {
|
||||
struct {
|
||||
int x;
|
||||
int y;
|
||||
} pos;
|
||||
struct {
|
||||
int width;
|
||||
int height;
|
||||
} size;
|
||||
struct {
|
||||
double x;
|
||||
double y;
|
||||
} scroll;
|
||||
struct {
|
||||
int key;
|
||||
int scancode;
|
||||
int mods;
|
||||
} keyboard;
|
||||
struct {
|
||||
int button;
|
||||
int mods;
|
||||
} mouse;
|
||||
unsigned int codepoint;
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
struct {
|
||||
char** paths;
|
||||
int count;
|
||||
} file;
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
struct {
|
||||
float x;
|
||||
float y;
|
||||
} scale;
|
||||
#endif
|
||||
};
|
||||
} GLEQevent;
|
||||
|
||||
GLEQDEF void gleqInit(void);
|
||||
GLEQDEF void gleqTrackWindow(GLFWwindow* window);
|
||||
|
||||
GLEQDEF int gleqNextEvent(GLEQevent* event);
|
||||
GLEQDEF void gleqFreeEvent(GLEQevent* event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef GLEQ_IMPLEMENTATION
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifndef GLEQ_CAPACITY
|
||||
#define GLEQ_CAPACITY 1024
|
||||
#endif
|
||||
|
||||
static struct
|
||||
{
|
||||
GLEQevent events[GLEQ_CAPACITY];
|
||||
size_t head;
|
||||
size_t tail;
|
||||
} gleq_queue = { {0}, 0, 0 };
|
||||
|
||||
static char* gleq_strdup(const char* string)
|
||||
{
|
||||
const size_t size = strlen(string) + 1;
|
||||
char* result = (char*) malloc(size);
|
||||
memcpy(result, string, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
static GLEQevent* gleq_new_event(void)
|
||||
{
|
||||
GLEQevent* event = gleq_queue.events + gleq_queue.head;
|
||||
gleq_queue.head = (gleq_queue.head + 1) % GLEQ_CAPACITY;
|
||||
assert(gleq_queue.head != gleq_queue.tail);
|
||||
memset(event, 0, sizeof(GLEQevent));
|
||||
return event;
|
||||
}
|
||||
|
||||
static void gleq_window_pos_callback(GLFWwindow* window, int x, int y)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_MOVED;
|
||||
event->window = window;
|
||||
event->pos.x = x;
|
||||
event->pos.y = y;
|
||||
}
|
||||
|
||||
static void gleq_window_size_callback(GLFWwindow* window, int width, int height)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_RESIZED;
|
||||
event->window = window;
|
||||
event->size.width = width;
|
||||
event->size.height = height;
|
||||
}
|
||||
|
||||
static void gleq_window_close_callback(GLFWwindow* window)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_CLOSED;
|
||||
event->window = window;
|
||||
}
|
||||
|
||||
static void gleq_window_refresh_callback(GLFWwindow* window)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_WINDOW_REFRESH;
|
||||
event->window = window;
|
||||
}
|
||||
|
||||
static void gleq_window_focus_callback(GLFWwindow* window, int focused)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (focused)
|
||||
event->type = GLEQ_WINDOW_FOCUSED;
|
||||
else
|
||||
event->type = GLEQ_WINDOW_DEFOCUSED;
|
||||
}
|
||||
|
||||
static void gleq_window_iconify_callback(GLFWwindow* window, int iconified)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (iconified)
|
||||
event->type = GLEQ_WINDOW_ICONIFIED;
|
||||
else
|
||||
event->type = GLEQ_WINDOW_UNICONIFIED;
|
||||
}
|
||||
|
||||
static void gleq_framebuffer_size_callback(GLFWwindow* window, int width, int height)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_FRAMEBUFFER_RESIZED;
|
||||
event->window = window;
|
||||
event->size.width = width;
|
||||
event->size.height = height;
|
||||
}
|
||||
|
||||
static void gleq_mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
event->mouse.button = button;
|
||||
event->mouse.mods = mods;
|
||||
|
||||
if (action == GLFW_PRESS)
|
||||
event->type = GLEQ_BUTTON_PRESSED;
|
||||
else if (action == GLFW_RELEASE)
|
||||
event->type = GLEQ_BUTTON_RELEASED;
|
||||
}
|
||||
|
||||
static void gleq_cursor_pos_callback(GLFWwindow* window, double x, double y)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_CURSOR_MOVED;
|
||||
event->window = window;
|
||||
event->pos.x = (int) x;
|
||||
event->pos.y = (int) y;
|
||||
}
|
||||
|
||||
static void gleq_cursor_enter_callback(GLFWwindow* window, int entered)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (entered)
|
||||
event->type = GLEQ_CURSOR_ENTERED;
|
||||
else
|
||||
event->type = GLEQ_CURSOR_LEFT;
|
||||
}
|
||||
|
||||
static void gleq_scroll_callback(GLFWwindow* window, double x, double y)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_SCROLLED;
|
||||
event->window = window;
|
||||
event->scroll.x = x;
|
||||
event->scroll.y = y;
|
||||
}
|
||||
|
||||
static void gleq_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
event->keyboard.key = key;
|
||||
event->keyboard.scancode = scancode;
|
||||
event->keyboard.mods = mods;
|
||||
|
||||
if (action == GLFW_PRESS)
|
||||
event->type = GLEQ_KEY_PRESSED;
|
||||
else if (action == GLFW_RELEASE)
|
||||
event->type = GLEQ_KEY_RELEASED;
|
||||
else if (action == GLFW_REPEAT)
|
||||
event->type = GLEQ_KEY_REPEATED;
|
||||
}
|
||||
|
||||
static void gleq_char_callback(GLFWwindow* window, unsigned int codepoint)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_CODEPOINT_INPUT;
|
||||
event->window = window;
|
||||
event->codepoint = codepoint;
|
||||
}
|
||||
|
||||
static void gleq_monitor_callback(GLFWmonitor* monitor, int action)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->monitor = monitor;
|
||||
|
||||
if (action == GLFW_CONNECTED)
|
||||
event->type = GLEQ_MONITOR_CONNECTED;
|
||||
else if (action == GLFW_DISCONNECTED)
|
||||
event->type = GLEQ_MONITOR_DISCONNECTED;
|
||||
}
|
||||
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
static void gleq_file_drop_callback(GLFWwindow* window, int count, const char** paths)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->type = GLEQ_FILE_DROPPED;
|
||||
event->window = window;
|
||||
event->file.paths = (char**) malloc(count * sizeof(char*));
|
||||
event->file.count = count;
|
||||
|
||||
while (count--)
|
||||
event->file.paths[count] = gleq_strdup(paths[count]);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if GLFW_VERSION_MINOR >= 2
|
||||
static void gleq_joystick_callback(int jid, int action)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->joystick = jid;
|
||||
|
||||
if (action == GLFW_CONNECTED)
|
||||
event->type = GLEQ_JOYSTICK_CONNECTED;
|
||||
else if (action == GLFW_DISCONNECTED)
|
||||
event->type = GLEQ_JOYSTICK_DISCONNECTED;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
static void gleq_window_maximize_callback(GLFWwindow* window, int maximized)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
|
||||
if (maximized)
|
||||
event->type = GLEQ_WINDOW_MAXIMIZED;
|
||||
else
|
||||
event->type = GLEQ_WINDOW_UNMAXIMIZED;
|
||||
}
|
||||
|
||||
static void gleq_window_content_scale_callback(GLFWwindow* window, float xscale, float yscale)
|
||||
{
|
||||
GLEQevent* event = gleq_new_event();
|
||||
event->window = window;
|
||||
event->type = GLEQ_WINDOW_SCALE_CHANGED;
|
||||
event->scale.x = xscale;
|
||||
event->scale.y = yscale;
|
||||
}
|
||||
#endif
|
||||
|
||||
GLEQDEF void gleqInit(void)
|
||||
{
|
||||
glfwSetMonitorCallback(gleq_monitor_callback);
|
||||
#if GLFW_VERSION_MINOR >= 2
|
||||
glfwSetJoystickCallback(gleq_joystick_callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
GLEQDEF void gleqTrackWindow(GLFWwindow* window)
|
||||
{
|
||||
glfwSetWindowPosCallback(window, gleq_window_pos_callback);
|
||||
glfwSetWindowSizeCallback(window, gleq_window_size_callback);
|
||||
glfwSetWindowCloseCallback(window, gleq_window_close_callback);
|
||||
glfwSetWindowRefreshCallback(window, gleq_window_refresh_callback);
|
||||
glfwSetWindowFocusCallback(window, gleq_window_focus_callback);
|
||||
glfwSetWindowIconifyCallback(window, gleq_window_iconify_callback);
|
||||
glfwSetFramebufferSizeCallback(window, gleq_framebuffer_size_callback);
|
||||
glfwSetMouseButtonCallback(window, gleq_mouse_button_callback);
|
||||
glfwSetCursorPosCallback(window, gleq_cursor_pos_callback);
|
||||
glfwSetCursorEnterCallback(window, gleq_cursor_enter_callback);
|
||||
glfwSetScrollCallback(window, gleq_scroll_callback);
|
||||
glfwSetKeyCallback(window, gleq_key_callback);
|
||||
glfwSetCharCallback(window, gleq_char_callback);
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
glfwSetDropCallback(window, gleq_file_drop_callback);
|
||||
#endif
|
||||
#if GLFW_VERSION_MINOR >= 3
|
||||
glfwSetWindowMaximizeCallback(window, gleq_window_maximize_callback);
|
||||
glfwSetWindowContentScaleCallback(window, gleq_window_content_scale_callback);
|
||||
#endif
|
||||
}
|
||||
|
||||
GLEQDEF int gleqNextEvent(GLEQevent* event)
|
||||
{
|
||||
memset(event, 0, sizeof(GLEQevent));
|
||||
|
||||
if (gleq_queue.head != gleq_queue.tail)
|
||||
{
|
||||
*event = gleq_queue.events[gleq_queue.tail];
|
||||
gleq_queue.tail = (gleq_queue.tail + 1) % GLEQ_CAPACITY;
|
||||
}
|
||||
|
||||
return event->type != GLEQ_NONE;
|
||||
}
|
||||
|
||||
GLEQDEF void gleqFreeEvent(GLEQevent* event)
|
||||
{
|
||||
#if GLFW_VERSION_MINOR >= 1
|
||||
if (event->type == GLEQ_FILE_DROPPED)
|
||||
{
|
||||
while (event->file.count--)
|
||||
free(event->file.paths[event->file.count]);
|
||||
|
||||
free(event->file.paths);
|
||||
}
|
||||
#endif
|
||||
|
||||
memset(event, 0, sizeof(GLEQevent));
|
||||
}
|
||||
|
||||
#endif /* GLEQ_IMPLEMENTATION */
|
||||
|
||||
#endif /* GLEQ_HEADER_FILE */
|
|
@ -0,0 +1,69 @@
|
|||
local core = require "core"
|
||||
local command = {}
|
||||
|
||||
command.map = {}
|
||||
|
||||
local always_true = function() return true end
|
||||
|
||||
|
||||
function command.add(predicate, map)
|
||||
predicate = predicate or always_true
|
||||
if type(predicate) == "string" then
|
||||
predicate = require(predicate)
|
||||
end
|
||||
if type(predicate) == "table" then
|
||||
local class = predicate
|
||||
predicate = function() return core.active_view:is(class) end
|
||||
end
|
||||
for name, fn in pairs(map) do
|
||||
assert(not command.map[name], "command already exists: " .. name)
|
||||
command.map[name] = { predicate = predicate, perform = fn }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function capitalize_first(str)
|
||||
return str:sub(1, 1):upper() .. str:sub(2)
|
||||
end
|
||||
|
||||
function command.prettify_name(name)
|
||||
return name:gsub(":", ": "):gsub("-", " "):gsub("%S+", capitalize_first)
|
||||
end
|
||||
|
||||
|
||||
function command.get_all_valid()
|
||||
local res = {}
|
||||
for name, cmd in pairs(command.map) do
|
||||
if cmd.predicate() then
|
||||
table.insert(res, name)
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function perform(name)
|
||||
local cmd = command.map[name]
|
||||
if cmd and cmd.predicate() then
|
||||
cmd.perform()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function command.perform(...)
|
||||
local ok, res = core.try(perform, ...)
|
||||
return not ok or res
|
||||
end
|
||||
|
||||
|
||||
function command.add_defaults()
|
||||
local reg = { "core", "root", "command", "doc", "findreplace" }
|
||||
for _, name in ipairs(reg) do
|
||||
require("core.commands." .. name)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return command
|
|
@ -0,0 +1,30 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
local function has_commandview()
|
||||
return core.active_view:is(CommandView)
|
||||
end
|
||||
|
||||
|
||||
command.add(has_commandview, {
|
||||
["command:submit"] = function()
|
||||
core.active_view:submit()
|
||||
end,
|
||||
|
||||
["command:complete"] = function()
|
||||
core.active_view:complete()
|
||||
end,
|
||||
|
||||
["command:escape"] = function()
|
||||
core.active_view:exit()
|
||||
end,
|
||||
|
||||
["command:select-previous"] = function()
|
||||
core.active_view:move_suggestion_idx(1)
|
||||
end,
|
||||
|
||||
["command:select-next"] = function()
|
||||
core.active_view:move_suggestion_idx(-1)
|
||||
end,
|
||||
})
|
|
@ -0,0 +1,105 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local LogView = require "core.logview"
|
||||
|
||||
|
||||
local fullscreen = false
|
||||
|
||||
command.add(nil, {
|
||||
["core:quit"] = function()
|
||||
core.quit()
|
||||
end,
|
||||
|
||||
["core:force-quit"] = function()
|
||||
core.quit(true)
|
||||
end,
|
||||
|
||||
["core:toggle-fullscreen"] = function()
|
||||
fullscreen = not fullscreen
|
||||
system.set_window_mode(fullscreen and "fullscreen" or "normal")
|
||||
end,
|
||||
|
||||
["core:reload-module"] = function()
|
||||
core.command_view:enter("Reload Module", function(text, item)
|
||||
local text = item and item.text or text
|
||||
core.reload_module(text)
|
||||
core.log("Reloaded module %q", text)
|
||||
end, function(text)
|
||||
local items = {}
|
||||
for name in pairs(package.loaded) do
|
||||
table.insert(items, name)
|
||||
end
|
||||
return common.fuzzy_match(items, text)
|
||||
end)
|
||||
end,
|
||||
|
||||
["core:find-command"] = function()
|
||||
local commands = command.get_all_valid()
|
||||
core.command_view:enter("Do Command", function(text, item)
|
||||
if item then
|
||||
command.perform(item.command)
|
||||
end
|
||||
end, function(text)
|
||||
local res = common.fuzzy_match(commands, text)
|
||||
for i, name in ipairs(res) do
|
||||
res[i] = {
|
||||
text = command.prettify_name(name),
|
||||
info = keymap.get_binding(name),
|
||||
command = name,
|
||||
}
|
||||
end
|
||||
return res
|
||||
end)
|
||||
end,
|
||||
|
||||
["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)
|
||||
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))
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["core:open-log"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
node:add_view(LogView())
|
||||
end,
|
||||
|
||||
["core:open-user-module"] = function()
|
||||
if system.get_file_info(EXEDIR .. "/data/user/init.lua") then --< @r-lyeh
|
||||
core.root_view:open_doc(core.open_doc(EXEDIR .. "/data/user/init.lua"))
|
||||
else
|
||||
core.root_view:open_doc(core.open_doc(DATADIR .. "/data/user/init.lua"))
|
||||
end
|
||||
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,
|
||||
})
|
|
@ -0,0 +1,392 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local translate = require "core.doc.translate"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
|
||||
local function dv()
|
||||
return core.active_view
|
||||
end
|
||||
|
||||
|
||||
local function doc()
|
||||
return core.active_view.doc
|
||||
end
|
||||
|
||||
|
||||
local function get_indent_string()
|
||||
if config.tab_type == "hard" then
|
||||
return "\t"
|
||||
end
|
||||
return string.rep(" ", config.indent_size)
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
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, skip_empty)
|
||||
local line1, col1, line2, col2, swap = doc():get_selection(true)
|
||||
for line = line1, line2 do
|
||||
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)
|
||||
end
|
||||
end
|
||||
doc():set_selection(line1, col1 - #text, line2, col2 - #text, swap)
|
||||
end
|
||||
|
||||
|
||||
local function append_line_if_last_line(line)
|
||||
if line >= #doc().lines then
|
||||
doc():insert(line, math.huge, "\n")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function save(filename)
|
||||
doc():save(filename)
|
||||
core.log("Saved \"%s\"", doc().filename)
|
||||
end
|
||||
|
||||
|
||||
local commands = {
|
||||
["doc:undo"] = function()
|
||||
doc():undo()
|
||||
end,
|
||||
|
||||
["doc:redo"] = function()
|
||||
doc():redo()
|
||||
end,
|
||||
|
||||
["doc:cut"] = function()
|
||||
if doc():has_selection() then
|
||||
local text = doc():get_text(doc():get_selection())
|
||||
system.set_clipboard(text)
|
||||
doc():delete_to(0)
|
||||
--< https://github.com/rxi/lite/pull/209/commits/479c58fb3dbaa0467afeffa815dd6e2a425281fa
|
||||
else
|
||||
local line = doc():get_selection()
|
||||
local text = doc().lines[line]
|
||||
system.set_clipboard(text)
|
||||
core.line_in_clipboard = text
|
||||
if line < #doc().lines then
|
||||
doc():remove(line, 1, line + 1, 1)
|
||||
else
|
||||
doc():remove(line - 1, math.huge, line, math.huge)
|
||||
end
|
||||
end
|
||||
--<
|
||||
end,
|
||||
|
||||
["doc:copy"] = function()
|
||||
--< https://github.com/rxi/lite/pull/209/commits/479c58fb3dbaa0467afeffa815dd6e2a425281fa
|
||||
local line1, col1, line2, col2, text = doc():get_selection()
|
||||
if doc():has_selection() then
|
||||
text = doc():get_text(line1, col1, line2, col2)
|
||||
else
|
||||
text = doc().lines[line1]
|
||||
core.line_in_clipboard = text
|
||||
end
|
||||
--<
|
||||
system.set_clipboard(text)
|
||||
end,
|
||||
|
||||
["doc:paste"] = function()
|
||||
--< https://github.com/rxi/lite/pull/209/commits/479c58fb3dbaa0467afeffa815dd6e2a425281fa
|
||||
local clipboard = system.get_clipboard():gsub("\r", "")
|
||||
if core.line_in_clipboard == clipboard then
|
||||
local line, col = doc():get_selection()
|
||||
doc():insert(line, 1, clipboard)
|
||||
doc():set_selection(line+1, col)
|
||||
else
|
||||
doc():text_input(clipboard)
|
||||
end
|
||||
--<
|
||||
end,
|
||||
|
||||
["doc:newline"] = function()
|
||||
local line, col = doc():get_selection()
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
if col <= #indent then
|
||||
indent = indent:sub(#indent + 2 - col)
|
||||
end
|
||||
doc():text_input("\n" .. indent)
|
||||
end,
|
||||
|
||||
["doc:newline-below"] = function()
|
||||
local line = doc():get_selection()
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
doc():insert(line, math.huge, "\n" .. indent)
|
||||
doc():set_selection(line + 1, math.huge)
|
||||
end,
|
||||
|
||||
["doc:newline-above"] = function()
|
||||
local line = doc():get_selection()
|
||||
local indent = doc().lines[line]:match("^[\t ]*")
|
||||
doc():insert(line, 1, indent .. "\n")
|
||||
doc():set_selection(line, math.huge)
|
||||
end,
|
||||
|
||||
["doc:delete"] = function()
|
||||
local line, col = doc():get_selection()
|
||||
if not doc():has_selection() and doc().lines[line]:find("^%s*$", col) then
|
||||
doc():remove(line, col, line, math.huge)
|
||||
end
|
||||
doc():delete_to(translate.next_char)
|
||||
end,
|
||||
|
||||
["doc:backspace"] = function()
|
||||
local line, col = doc():get_selection()
|
||||
if not doc():has_selection() then
|
||||
local text = doc():get_text(line, 1, line, col)
|
||||
if #text >= config.indent_size and text:find("^ *$") then
|
||||
doc():delete_to(0, -config.indent_size)
|
||||
return
|
||||
end
|
||||
end
|
||||
doc():delete_to(translate.previous_char)
|
||||
end,
|
||||
|
||||
["doc:select-all"] = function()
|
||||
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)
|
||||
doc():set_selection(line1, 1, line2 + 1, 1, swap)
|
||||
end,
|
||||
|
||||
["doc:select-word"] = function()
|
||||
local line1, col1 = doc():get_selection(true)
|
||||
local line1, col1 = translate.start_of_word(doc(), line1, col1)
|
||||
local line2, col2 = translate.end_of_word(doc(), line1, col1)
|
||||
doc():set_selection(line2, col2, line1, col1)
|
||||
end,
|
||||
|
||||
["doc:join-lines"] = function()
|
||||
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)
|
||||
--< https://github.com/rxi/lite/pull/209/commits/479c58fb3dbaa0467afeffa815dd6e2a425281fa
|
||||
text = text:gsub("\n[\t ]*", " ")
|
||||
--<
|
||||
doc():insert(line1, 1, text)
|
||||
doc():remove(line1, #text + 1, line2, math.huge)
|
||||
if doc():has_selection() then
|
||||
doc():set_selection(line1, math.huge)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:indent"] = function()
|
||||
local text = get_indent_string()
|
||||
if doc():has_selection() then
|
||||
insert_at_start_of_selected_lines(text)
|
||||
else
|
||||
doc():text_input(text)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:unindent"] = function()
|
||||
local text = get_indent_string()
|
||||
remove_from_start_of_selected_lines(text)
|
||||
end,
|
||||
|
||||
["doc:duplicate-lines"] = function()
|
||||
local line1, col1, line2, col2, swap = doc():get_selection(true)
|
||||
append_line_if_last_line(line2)
|
||||
local text = doc():get_text(line1, 1, line2 + 1, 1)
|
||||
doc():insert(line2 + 1, 1, text)
|
||||
local n = line2 - line1 + 1
|
||||
doc():set_selection(line1 + n, col1, line2 + n, col2, swap)
|
||||
end,
|
||||
|
||||
["doc:delete-lines"] = function()
|
||||
local line1, col1, line2 = doc():get_selection(true)
|
||||
append_line_if_last_line(line2)
|
||||
doc():remove(line1, 1, line2 + 1, 1)
|
||||
doc():set_selection(line1, col1)
|
||||
end,
|
||||
|
||||
["doc:move-lines-up"] = function()
|
||||
local line1, col1, line2, col2, swap = doc():get_selection(true)
|
||||
append_line_if_last_line(line2)
|
||||
if line1 > 1 then
|
||||
local text = doc().lines[line1 - 1]
|
||||
doc():insert(line2 + 1, 1, text)
|
||||
doc():remove(line1 - 1, 1, line1, 1)
|
||||
doc():set_selection(line1 - 1, col1, line2 - 1, col2, swap)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:move-lines-down"] = function()
|
||||
local line1, col1, line2, col2, swap = doc():get_selection(true)
|
||||
append_line_if_last_line(line2 + 1)
|
||||
if line2 < #doc().lines then
|
||||
local text = doc().lines[line2 + 1]
|
||||
doc():remove(line2 + 1, 1, line2 + 2, 1)
|
||||
doc():insert(line1, 1, text)
|
||||
doc():set_selection(line1 + 1, col1, line2 + 1, col2, swap)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:toggle-line-comments"] = function()
|
||||
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 text = doc().lines[line]
|
||||
if text:find("%S") and text:find(comment_text, 1, true) ~= 1 then
|
||||
uncomment = false
|
||||
end
|
||||
end
|
||||
if uncomment then
|
||||
remove_from_start_of_selected_lines(comment_text, true)
|
||||
else
|
||||
insert_at_start_of_selected_lines(comment_text, true)
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:upper-case"] = function()
|
||||
doc():replace(string.upper)
|
||||
end,
|
||||
|
||||
["doc:lower-case"] = function()
|
||||
doc():replace(string.lower)
|
||||
end,
|
||||
|
||||
["doc:go-to-line"] = function()
|
||||
local dv = dv()
|
||||
|
||||
local items
|
||||
local function init_items()
|
||||
if items then return end
|
||||
items = {}
|
||||
local mt = { __tostring = function(x) return x.text end }
|
||||
for i, line in ipairs(dv.doc.lines) do
|
||||
local item = { text = line:sub(1, -2), line = i, info = "line: " .. i }
|
||||
table.insert(items, setmetatable(item, mt))
|
||||
end
|
||||
end
|
||||
|
||||
core.command_view:enter("Go To Line", function(text, item)
|
||||
local line = item and item.line or tonumber(text)
|
||||
if not line then
|
||||
core.error("Invalid line number or unmatched string")
|
||||
return
|
||||
end
|
||||
dv.doc:set_selection(line, 1 )
|
||||
dv:scroll_to_line(line, true)
|
||||
|
||||
end, function(text)
|
||||
if not text:find("^%d*$") then
|
||||
init_items()
|
||||
return common.fuzzy_match(items, text)
|
||||
end
|
||||
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)
|
||||
end
|
||||
core.command_view:enter("Save As", function(filename)
|
||||
save(filename)
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
["doc:save"] = function()
|
||||
if doc().filename then
|
||||
save()
|
||||
else
|
||||
command.perform("doc:save-as")
|
||||
end
|
||||
end,
|
||||
|
||||
["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,
|
||||
}
|
||||
|
||||
|
||||
local translations = {
|
||||
["previous-char"] = translate.previous_char,
|
||||
["next-char"] = translate.next_char,
|
||||
["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,
|
||||
["end-of-line"] = translate.end_of_line,
|
||||
["start-of-word"] = translate.start_of_word,
|
||||
["end-of-word"] = translate.end_of_word,
|
||||
["previous-line"] = DocView.translate.previous_line,
|
||||
["next-line"] = DocView.translate.next_line,
|
||||
["previous-page"] = DocView.translate.previous_page,
|
||||
["next-page"] = DocView.translate.next_page,
|
||||
}
|
||||
|
||||
for name, fn in pairs(translations) do
|
||||
commands["doc:move-to-" .. name] = function() doc():move_to(fn, dv()) end
|
||||
commands["doc:select-to-" .. name] = function() doc():select_to(fn, dv()) end
|
||||
commands["doc:delete-to-" .. name] = function() doc():delete_to(fn, dv()) end
|
||||
end
|
||||
|
||||
commands["doc:move-to-previous-char"] = function()
|
||||
if doc():has_selection() then
|
||||
local line, col = doc():get_selection(true)
|
||||
doc():set_selection(line, col)
|
||||
else
|
||||
doc():move_to(translate.previous_char)
|
||||
end
|
||||
end
|
||||
|
||||
commands["doc:move-to-next-char"] = function()
|
||||
if doc():has_selection() then
|
||||
local _, _, line, col = doc():get_selection(true)
|
||||
doc():set_selection(line, col)
|
||||
else
|
||||
doc():move_to(translate.next_char)
|
||||
end
|
||||
end
|
||||
|
||||
command.add("core.docview", commands)
|
|
@ -0,0 +1,170 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local search = require "core.doc.search"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
local max_previous_finds = 50
|
||||
|
||||
|
||||
local function doc()
|
||||
return core.active_view.doc
|
||||
end
|
||||
|
||||
|
||||
local previous_finds
|
||||
local last_doc
|
||||
local last_fn, last_text
|
||||
|
||||
|
||||
local function push_previous_find(doc, sel)
|
||||
if last_doc ~= doc then
|
||||
last_doc = doc
|
||||
previous_finds = {}
|
||||
end
|
||||
if #previous_finds >= max_previous_finds then
|
||||
table.remove(previous_finds, 1)
|
||||
end
|
||||
table.insert(previous_finds, sel or { doc:get_selection() })
|
||||
end
|
||||
|
||||
|
||||
local function find(label, search_fn)
|
||||
local dv = core.active_view
|
||||
local sel = { dv.doc:get_selection() }
|
||||
local text = dv.doc:get_text(table.unpack(sel))
|
||||
local found = false
|
||||
|
||||
core.command_view:set_text(text, true)
|
||||
|
||||
core.command_view:enter(label, function(text)
|
||||
if found then
|
||||
last_fn, last_text = search_fn, text
|
||||
previous_finds = {}
|
||||
push_previous_find(dv.doc, sel)
|
||||
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 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(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)
|
||||
return fn(text, old, new)
|
||||
end)
|
||||
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)
|
||||
local opt = { wrap = true, no_case = true }
|
||||
return search.find(doc, line, col, text, opt)
|
||||
end)
|
||||
end,
|
||||
|
||||
["find-replace:find-pattern"] = function()
|
||||
find("Find Text Pattern", function(doc, line, col, text)
|
||||
local opt = { wrap = true, no_case = true, pattern = true }
|
||||
return search.find(doc, line, col, text, opt)
|
||||
end)
|
||||
end,
|
||||
|
||||
["find-replace:repeat-find"] = function()
|
||||
if not last_fn then
|
||||
core.error("No find to continue from")
|
||||
else
|
||||
local line, col = doc():get_selection()
|
||||
local line1, col1, line2, col2 = last_fn(doc(), line, col, last_text)
|
||||
if line1 then
|
||||
push_previous_find(doc())
|
||||
doc():set_selection(line2, col2, line1, col1)
|
||||
core.active_view:scroll_to_line(line2, true)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["find-replace:previous-find"] = function()
|
||||
local sel = table.remove(previous_finds)
|
||||
if not sel or doc() ~= last_doc then
|
||||
core.error("No previous finds")
|
||||
return
|
||||
end
|
||||
doc():set_selection(table.unpack(sel))
|
||||
core.active_view:scroll_to_line(sel[3], true)
|
||||
end,
|
||||
|
||||
["find-replace:replace"] = function()
|
||||
replace("Text", "", function(text, old, new)
|
||||
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
||||
end)
|
||||
end,
|
||||
|
||||
["find-replace:replace-pattern"] = function()
|
||||
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,
|
||||
})
|
|
@ -0,0 +1,105 @@
|
|||
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 = {
|
||||
["root:close"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
node:close_active_view(core.root_view.root_node)
|
||||
end,
|
||||
|
||||
["root:switch-to-previous-tab"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local idx = node:get_view_idx(core.active_view)
|
||||
idx = idx - 1
|
||||
if idx < 1 then idx = #node.views end
|
||||
node:set_active_view(node.views[idx])
|
||||
end,
|
||||
|
||||
["root:switch-to-next-tab"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local idx = node:get_view_idx(core.active_view)
|
||||
idx = idx + 1
|
||||
if idx > #node.views then idx = 1 end
|
||||
node:set_active_view(node.views[idx])
|
||||
end,
|
||||
|
||||
["root:move-tab-left"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local idx = node:get_view_idx(core.active_view)
|
||||
if idx > 1 then
|
||||
table.remove(node.views, idx)
|
||||
table.insert(node.views, idx - 1, core.active_view)
|
||||
end
|
||||
end,
|
||||
|
||||
["root:move-tab-right"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local idx = node:get_view_idx(core.active_view)
|
||||
if idx < #node.views then
|
||||
table.remove(node.views, idx)
|
||||
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,
|
||||
}
|
||||
|
||||
|
||||
for i = 1, 9 do
|
||||
t["root:switch-to-tab-" .. i] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local view = node.views[i]
|
||||
if view then
|
||||
node:set_active_view(view)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for _, dir in ipairs { "left", "right", "up", "down" } do
|
||||
t["root:split-" .. dir] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local av = node.active_view
|
||||
node:split(dir)
|
||||
if av:is(DocView) then
|
||||
core.root_view:open_doc(av.doc)
|
||||
end
|
||||
end
|
||||
|
||||
t["root:switch-to-" .. dir] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local x, y
|
||||
if dir == "left" or dir == "right" then
|
||||
y = node.position.y + node.size.y / 2
|
||||
x = node.position.x + (dir == "left" and -1 or node.size.x + style.divider_size)
|
||||
else
|
||||
x = node.position.x + node.size.x / 2
|
||||
y = node.position.y + (dir == "up" and -1 or node.size.y + style.divider_size)
|
||||
end
|
||||
local node = core.root_view.root_node:get_child_overlapping_point(x, y)
|
||||
if not node:get_locked_size() then
|
||||
core.set_active_view(node.active_view)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
command.add(function()
|
||||
local node = core.root_view:get_active_node()
|
||||
return not node:get_locked_size()
|
||||
end, t)
|
|
@ -0,0 +1,256 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local style = require "core.style"
|
||||
local Doc = require "core.doc"
|
||||
local DocView = require "core.docview"
|
||||
local View = require "core.view"
|
||||
|
||||
|
||||
local SingleLineDoc = Doc:extend()
|
||||
|
||||
function SingleLineDoc:insert(line, col, text)
|
||||
SingleLineDoc.super.insert(self, line, col, text:gsub("\n", ""))
|
||||
end
|
||||
|
||||
|
||||
local CommandView = DocView:extend()
|
||||
|
||||
local max_suggestions = 10
|
||||
|
||||
local noop = function() end
|
||||
|
||||
local default_state = {
|
||||
submit = noop,
|
||||
suggest = noop,
|
||||
cancel = noop,
|
||||
}
|
||||
|
||||
|
||||
function CommandView:new()
|
||||
CommandView.super.new(self, SingleLineDoc())
|
||||
self.suggestion_idx = 1
|
||||
self.suggestions = {}
|
||||
self.suggestions_height = 0
|
||||
self.last_change_id = 0
|
||||
self.gutter_width = 0
|
||||
self.gutter_text_brightness = 0
|
||||
self.selection_offset = 0
|
||||
self.state = default_state
|
||||
self.font = "font"
|
||||
self.size.y = 0
|
||||
self.label = ""
|
||||
end
|
||||
|
||||
|
||||
function CommandView:get_name()
|
||||
return View.get_name(self)
|
||||
end
|
||||
|
||||
|
||||
function CommandView:get_line_screen_position()
|
||||
local x = CommandView.super.get_line_screen_position(self, 1)
|
||||
local _, y = self:get_content_offset()
|
||||
local lh = self:get_line_height()
|
||||
return x, y + (self.size.y - lh) / 2
|
||||
end
|
||||
|
||||
|
||||
function CommandView:get_scrollable_size()
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
function CommandView:scroll_to_make_visible()
|
||||
-- no-op function to disable this functionality
|
||||
end
|
||||
|
||||
|
||||
function CommandView:get_text()
|
||||
return self.doc:get_text(1, 1, 1, math.huge)
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
function CommandView:move_suggestion_idx(dir)
|
||||
local n = self.suggestion_idx + dir
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
end
|
||||
|
||||
|
||||
function CommandView:complete()
|
||||
if #self.suggestions > 0 then
|
||||
self:set_text(self.suggestions[self.suggestion_idx].text)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function CommandView:submit()
|
||||
local suggestion = self.suggestions[self.suggestion_idx]
|
||||
local text = self:get_text()
|
||||
local submit = self.state.submit
|
||||
self:exit(true)
|
||||
submit(text, suggestion)
|
||||
end
|
||||
|
||||
|
||||
function CommandView:enter(text, submit, suggest, cancel)
|
||||
if self.state ~= default_state then
|
||||
return
|
||||
end
|
||||
self.state = {
|
||||
submit = submit or noop,
|
||||
suggest = suggest or noop,
|
||||
cancel = cancel or noop,
|
||||
}
|
||||
core.set_active_view(self)
|
||||
self:update_suggestions()
|
||||
self.gutter_text_brightness = 100
|
||||
self.label = text .. ": "
|
||||
end
|
||||
|
||||
|
||||
function CommandView:exit(submitted, inexplicit)
|
||||
if core.active_view == self then
|
||||
core.set_active_view(core.last_active_view)
|
||||
end
|
||||
local cancel = self.state.cancel
|
||||
self.state = default_state
|
||||
self.doc:reset()
|
||||
self.suggestions = {}
|
||||
if not submitted then cancel(not inexplicit) end
|
||||
end
|
||||
|
||||
|
||||
function CommandView:get_gutter_width()
|
||||
return self.gutter_width
|
||||
end
|
||||
|
||||
|
||||
function CommandView:get_suggestion_line_height()
|
||||
return self:get_font():get_height() + style.padding.y
|
||||
end
|
||||
|
||||
|
||||
function CommandView:update_suggestions()
|
||||
local t = self.state.suggest(self:get_text()) or {}
|
||||
local res = {}
|
||||
for i, item in ipairs(t) do
|
||||
if i == max_suggestions then
|
||||
break
|
||||
end
|
||||
if type(item) == "string" then
|
||||
item = { text = item }
|
||||
end
|
||||
res[i] = item
|
||||
end
|
||||
self.suggestions = res
|
||||
self.suggestion_idx = 1
|
||||
end
|
||||
|
||||
|
||||
function CommandView:update()
|
||||
CommandView.super.update(self)
|
||||
|
||||
if core.active_view ~= self and self.state ~= default_state then
|
||||
self:exit(false, true)
|
||||
end
|
||||
|
||||
-- update suggestions if text has changed
|
||||
if self.last_change_id ~= self.doc:get_change_id() then
|
||||
self:update_suggestions()
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
end
|
||||
|
||||
-- update gutter text color brightness
|
||||
self:move_towards("gutter_text_brightness", 0, 0.1)
|
||||
|
||||
-- update gutter width
|
||||
local dest = self:get_font():get_width(self.label) + style.padding.x
|
||||
if self.size.y <= 0 then
|
||||
self.gutter_width = dest
|
||||
else
|
||||
self:move_towards("gutter_width", dest)
|
||||
end
|
||||
|
||||
-- update suggestions box height
|
||||
local lh = self:get_suggestion_line_height()
|
||||
local dest = #self.suggestions * lh
|
||||
self:move_towards("suggestions_height", dest)
|
||||
|
||||
-- update suggestion cursor offset
|
||||
local dest = self.suggestion_idx * self:get_suggestion_line_height()
|
||||
self:move_towards("selection_offset", dest)
|
||||
|
||||
-- update size based on whether this is the active_view
|
||||
local dest = 0
|
||||
if self == core.active_view then
|
||||
dest = style.font:get_height() + style.padding.y * 2
|
||||
end
|
||||
self:move_towards(self.size, "y", dest)
|
||||
end
|
||||
|
||||
|
||||
function CommandView:draw_line_highlight()
|
||||
-- no-op function to disable this functionality
|
||||
end
|
||||
|
||||
|
||||
function CommandView:draw_line_gutter(idx, x, y)
|
||||
local yoffset = self:get_line_text_y_offset()
|
||||
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
|
||||
|
||||
|
||||
local function draw_suggestions_box(self)
|
||||
local lh = self:get_suggestion_line_height()
|
||||
local dh = style.divider_size
|
||||
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
|
||||
|
||||
-- draw suggestions background
|
||||
if #self.suggestions > 0 then
|
||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||
renderer.draw_rect(rx, ry - dh, rw, dh, style.divider)
|
||||
local y = self.position.y - self.selection_offset - dh
|
||||
renderer.draw_rect(rx, y, rw, lh, style.line_highlight)
|
||||
end
|
||||
|
||||
-- draw suggestion text
|
||||
core.push_clip_rect(rx, ry, rw, rh)
|
||||
for i, item in ipairs(self.suggestions) do
|
||||
local color = (i == self.suggestion_idx) and style.accent or style.text
|
||||
local y = self.position.y - i * lh - dh
|
||||
common.draw_text(self:get_font(), color, item.text, nil, x, y, 0, lh)
|
||||
|
||||
if item.info then
|
||||
local w = self.size.x - x - style.padding.x
|
||||
common.draw_text(self:get_font(), style.dim, item.info, "right", x, y, w, lh)
|
||||
end
|
||||
end
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
|
||||
function CommandView:draw()
|
||||
CommandView.super.draw(self)
|
||||
core.root_view:defer_draw(draw_suggestions_box, self)
|
||||
end
|
||||
|
||||
|
||||
return CommandView
|
|
@ -0,0 +1,147 @@
|
|||
local common = {}
|
||||
|
||||
|
||||
function common.is_utf8_cont(char)
|
||||
local byte = char:byte()
|
||||
return byte >= 0x80 and byte < 0xc0
|
||||
end
|
||||
|
||||
|
||||
function common.utf8_chars(text)
|
||||
return text:gmatch("[\0-\x7f\xc2-\xf4][\x80-\xbf]*")
|
||||
end
|
||||
|
||||
--< https://github.com/rxi/lite/issues/300
|
||||
function common.utf8_len(text)
|
||||
local len = 0
|
||||
for char in common.utf8_chars(text) do len = len + 1 end
|
||||
return len
|
||||
end
|
||||
--<
|
||||
|
||||
function common.clamp(n, lo, hi)
|
||||
return math.max(math.min(n, hi), lo)
|
||||
end
|
||||
|
||||
|
||||
function common.round(n)
|
||||
return n >= 0 and math.floor(n + 0.5) or math.ceil(n - 0.5)
|
||||
end
|
||||
|
||||
|
||||
function common.lerp(a, b, t)
|
||||
if type(a) ~= "table" then
|
||||
return a + (b - a) * t
|
||||
end
|
||||
local res = {}
|
||||
for k, v in pairs(b) do
|
||||
res[k] = common.lerp(a[k], v, t)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
function common.color(str)
|
||||
local r, g, b, a = str:match("#(%x%x)(%x%x)(%x%x)")
|
||||
if r then
|
||||
r = tonumber(r, 16)
|
||||
g = tonumber(g, 16)
|
||||
b = tonumber(b, 16)
|
||||
a = 1
|
||||
elseif str:match("rgba?%s*%([%d%s%.,]+%)") then
|
||||
local f = str:gmatch("[%d.]+")
|
||||
r = (f() or 0)
|
||||
g = (f() or 0)
|
||||
b = (f() or 0)
|
||||
a = f() or 1
|
||||
else
|
||||
error(string.format("bad color string '%s'", str))
|
||||
end
|
||||
return r, g, b, a * 0xff
|
||||
end
|
||||
|
||||
|
||||
local function compare_score(a, b)
|
||||
return a.score > b.score
|
||||
end
|
||||
|
||||
local function fuzzy_match_items(items, needle)
|
||||
local res = {}
|
||||
for _, item in ipairs(items) do
|
||||
local score = system.fuzzy_match(tostring(item), needle)
|
||||
if score then
|
||||
table.insert(res, { text = item, score = score })
|
||||
end
|
||||
end
|
||||
table.sort(res, compare_score)
|
||||
for i, item in ipairs(res) do
|
||||
res[i] = item.text
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
function common.fuzzy_match(haystack, needle)
|
||||
if type(haystack) == "table" then
|
||||
return fuzzy_match_items(haystack, needle)
|
||||
end
|
||||
return system.fuzzy_match(haystack, needle)
|
||||
end
|
||||
|
||||
|
||||
function common.path_suggest(text)
|
||||
local path, name = text:match("^(.-)([^/\\]*)$")
|
||||
local files = system.list_dir(path == "" and "." or path) or {}
|
||||
local res = {}
|
||||
for _, file in ipairs(files) do
|
||||
file = path .. file
|
||||
local info = system.get_file_info(file)
|
||||
if info then
|
||||
if info.type == "dir" then
|
||||
file = file .. PATHSEP
|
||||
end
|
||||
if file:lower():find(text:lower(), nil, true) == 1 then
|
||||
table.insert(res, file)
|
||||
end
|
||||
end
|
||||
end
|
||||
return res
|
||||
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
|
||||
x = x + (w - tw) / 2
|
||||
elseif align == "right" then
|
||||
x = x + (w - tw)
|
||||
end
|
||||
y = common.round(y + (h - th) / 2)
|
||||
return renderer.draw_text(font, text, x, y, color), y + th
|
||||
end
|
||||
|
||||
|
||||
function common.bench(name, fn, ...)
|
||||
local start = system.get_time()
|
||||
local res = fn(...)
|
||||
local t = system.get_time() - start
|
||||
local ms = t * 1000
|
||||
local per = (t / (1 / 60)) * 100
|
||||
print(string.format("*** %-16s : %8.3fms %6.2f%%", name, ms, per))
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
return common
|
|
@ -0,0 +1,24 @@
|
|||
local config = {}
|
||||
|
||||
config.project_scan_rate = 5000 --< @r-lyeh: 5 -> 5000: from 5ms to 5s
|
||||
config.fps = 60
|
||||
config.max_log_items = 80
|
||||
config.message_timeout = 3
|
||||
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.undo_merge_timeout = 0.3
|
||||
config.max_undos = 10000
|
||||
config.highlight_current_line = true
|
||||
config.line_height = 1.2
|
||||
config.indent_size = 2
|
||||
config.tab_type = "soft"
|
||||
config.line_limit = 80
|
||||
config.project_scan_depth = 5
|
||||
config.project_max_files_per_folder = 2000
|
||||
config.blink_period = 1.3 --< https://github.com/rxi/lite/issues/235
|
||||
config.tabs_allowed = true --< https://github.com/rxi/lite/issues/191
|
||||
|
||||
return config
|
|
@ -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
|
|
@ -0,0 +1,393 @@
|
|||
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"
|
||||
|
||||
|
||||
local Doc = Object:extend()
|
||||
|
||||
|
||||
local function split_lines(text)
|
||||
local res = {}
|
||||
for line in (text .. "\n"):gmatch("(.-)\n") do
|
||||
table.insert(res, line)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function splice(t, at, remove, insert)
|
||||
insert = insert or {}
|
||||
local offset = #insert - remove
|
||||
local old_len = #t
|
||||
if offset < 0 then
|
||||
for i = at - offset, old_len - offset do
|
||||
t[i + offset] = t[i]
|
||||
end
|
||||
elseif offset > 0 then
|
||||
for i = old_len, at, -1 do
|
||||
t[i + offset] = t[i]
|
||||
end
|
||||
end
|
||||
for i, item in ipairs(insert) do
|
||||
t[at + i - 1] = item
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:new(filename)
|
||||
self:reset()
|
||||
if filename then
|
||||
self:load(filename)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:reset()
|
||||
self.lines = { "\n" }
|
||||
self.selection = { a = { line=1, col=1 }, b = { line=1, col=1 } }
|
||||
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
|
||||
|
||||
|
||||
function Doc:load(filename)
|
||||
local fp = assert( io.open(filename, "rb") )
|
||||
self:reset()
|
||||
self.filename = filename
|
||||
self.lines = {}
|
||||
for line in fp:lines() do
|
||||
if line:byte(-1) == 13 then
|
||||
line = line:sub(1, -2)
|
||||
self.crlf = true
|
||||
end
|
||||
table.insert(self.lines, line .. "\n")
|
||||
end
|
||||
if #self.lines == 0 then
|
||||
table.insert(self.lines, "\n")
|
||||
end
|
||||
fp:close()
|
||||
self:reset_syntax()
|
||||
end
|
||||
|
||||
|
||||
function Doc:save(filename)
|
||||
filename = filename or assert(self.filename, "no filename set to default to")
|
||||
local fp = assert( io.open(filename, "wb") )
|
||||
for _, line in ipairs(self.lines) do
|
||||
if self.crlf then line = line:gsub("\n", "\r\n") end
|
||||
fp:write(line)
|
||||
end
|
||||
fp:close()
|
||||
self.filename = filename or self.filename
|
||||
self:reset_syntax()
|
||||
self:clean()
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_name()
|
||||
return self.filename or "unsaved"
|
||||
end
|
||||
|
||||
|
||||
function Doc:is_dirty()
|
||||
return self.clean_change_id ~= self:get_change_id()
|
||||
end
|
||||
|
||||
|
||||
function Doc:clean()
|
||||
self.clean_change_id = self:get_change_id()
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_change_id()
|
||||
return self.undo_stack.idx
|
||||
end
|
||||
|
||||
|
||||
function Doc:set_selection(line1, col1, line2, col2, swap)
|
||||
assert(not line2 == not col2, "expected 2 or 4 arguments")
|
||||
if swap then line1, col1, line2, col2 = line2, col2, line1, col1 end
|
||||
line1, col1 = self:sanitize_position(line1, col1)
|
||||
line2, col2 = self:sanitize_position(line2 or line1, col2 or col1)
|
||||
self.selection.a.line, self.selection.a.col = line1, col1
|
||||
self.selection.b.line, self.selection.b.col = line2, col2
|
||||
end
|
||||
|
||||
|
||||
local function sort_positions(line1, col1, line2, col2)
|
||||
if line1 > line2
|
||||
or line1 == line2 and col1 > col2 then
|
||||
return line2, col2, line1, col1, true
|
||||
end
|
||||
return line1, col1, line2, col2, false
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_selection(sort)
|
||||
local a, b = self.selection.a, self.selection.b
|
||||
if sort then
|
||||
return sort_positions(a.line, a.col, b.line, b.col)
|
||||
end
|
||||
return a.line, a.col, b.line, b.col
|
||||
end
|
||||
|
||||
|
||||
function Doc:has_selection()
|
||||
local a, b = self.selection.a, self.selection.b
|
||||
return not (a.line == b.line and a.col == b.col)
|
||||
end
|
||||
|
||||
|
||||
function Doc:sanitize_selection()
|
||||
self:set_selection(self:get_selection())
|
||||
end
|
||||
|
||||
|
||||
function Doc:sanitize_position(line, col)
|
||||
line = common.clamp(line, 1, #self.lines)
|
||||
col = common.clamp(col, 1, #self.lines[line])
|
||||
return line, col
|
||||
end
|
||||
|
||||
|
||||
local function position_offset_func(self, line, col, fn, ...)
|
||||
line, col = self:sanitize_position(line, col)
|
||||
return fn(self, line, col, ...)
|
||||
end
|
||||
|
||||
|
||||
local function position_offset_byte(self, line, col, offset)
|
||||
line, col = self:sanitize_position(line, col)
|
||||
col = col + offset
|
||||
while line > 1 and col < 1 do
|
||||
line = line - 1
|
||||
col = col + #self.lines[line]
|
||||
end
|
||||
while line < #self.lines and col > #self.lines[line] do
|
||||
col = col - #self.lines[line]
|
||||
line = line + 1
|
||||
end
|
||||
return self:sanitize_position(line, col)
|
||||
end
|
||||
|
||||
|
||||
local function position_offset_linecol(self, line, col, lineoffset, coloffset)
|
||||
return self:sanitize_position(line + lineoffset, col + coloffset)
|
||||
end
|
||||
|
||||
|
||||
function Doc:position_offset(line, col, ...)
|
||||
if type(...) ~= "number" then
|
||||
return position_offset_func(self, line, col, ...)
|
||||
elseif select("#", ...) == 1 then
|
||||
return position_offset_byte(self, line, col, ...)
|
||||
elseif select("#", ...) == 2 then
|
||||
return position_offset_linecol(self, line, col, ...)
|
||||
else
|
||||
error("bad number of arguments")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_text(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)
|
||||
if line1 == line2 then
|
||||
return self.lines[line1]:sub(col1, col2 - 1)
|
||||
end
|
||||
local lines = { self.lines[line1]:sub(col1) }
|
||||
for i = line1 + 1, line2 - 1 do
|
||||
table.insert(lines, self.lines[i])
|
||||
end
|
||||
table.insert(lines, self.lines[line2]:sub(1, col2 - 1))
|
||||
return table.concat(lines)
|
||||
end
|
||||
|
||||
|
||||
function Doc:get_char(line, col)
|
||||
line, col = self:sanitize_position(line, col)
|
||||
return self.lines[line]:sub(col, col)
|
||||
end
|
||||
|
||||
|
||||
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 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)
|
||||
local after = self.lines[line]:sub(col)
|
||||
for i = 1, #lines - 1 do
|
||||
lines[i] = lines[i] .. "\n"
|
||||
end
|
||||
lines[1] = before .. lines[1]
|
||||
lines[#lines] = lines[#lines] .. after
|
||||
|
||||
-- splice lines into line array
|
||||
splice(self.lines, line, 1, lines)
|
||||
|
||||
-- push undo
|
||||
local line2, col2 = self:position_offset(line, col, #text)
|
||||
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
|
||||
|
||||
|
||||
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||
-- push undo
|
||||
local text = self:get_text(line1, col1, line2, col2)
|
||||
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)
|
||||
local after = self.lines[line2]:sub(col2)
|
||||
|
||||
-- splice line into line array
|
||||
splice(self.lines, line1, line2 - line1 + 1, { before .. after })
|
||||
|
||||
-- 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(line1, col1, line2, col2)
|
||||
self.redo_stack = { idx = 1 }
|
||||
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
|
||||
|
||||
|
||||
function Doc:undo()
|
||||
pop_undo(self, self.undo_stack, self.redo_stack)
|
||||
end
|
||||
|
||||
|
||||
function Doc:redo()
|
||||
pop_undo(self, self.redo_stack, self.undo_stack)
|
||||
end
|
||||
|
||||
|
||||
function Doc:text_input(text)
|
||||
if self:has_selection() then
|
||||
self:delete_to()
|
||||
end
|
||||
local line, col = self:get_selection()
|
||||
self:insert(line, col, text)
|
||||
self:move_to(#text)
|
||||
end
|
||||
|
||||
|
||||
function Doc:replace(fn)
|
||||
local line1, col1, line2, col2, swap
|
||||
local had_selection = self:has_selection()
|
||||
if had_selection then
|
||||
line1, col1, line2, col2, swap = self:get_selection(true)
|
||||
else
|
||||
line1, col1, line2, col2 = 1, 1, #self.lines, #self.lines[#self.lines]
|
||||
end
|
||||
local old_text = self:get_text(line1, col1, line2, col2)
|
||||
local new_text, n = fn(old_text)
|
||||
if old_text ~= new_text then
|
||||
self:insert(line2, col2, new_text)
|
||||
self:remove(line1, col1, line2, col2)
|
||||
if had_selection then
|
||||
line2, col2 = self:position_offset(line1, col1, #new_text)
|
||||
self:set_selection(line1, col1, line2, col2, swap)
|
||||
end
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
|
||||
function Doc:delete_to(...)
|
||||
local line, col = self:get_selection(true)
|
||||
if self:has_selection() then
|
||||
self:remove(self:get_selection())
|
||||
else
|
||||
local line2, col2 = self:position_offset(line, col, ...)
|
||||
self:remove(line, col, line2, col2)
|
||||
line, col = sort_positions(line, col, line2, col2)
|
||||
end
|
||||
self:set_selection(line, col)
|
||||
end
|
||||
|
||||
|
||||
function Doc:move_to(...)
|
||||
local line, col = self:get_selection()
|
||||
self:set_selection(self:position_offset(line, col, ...))
|
||||
end
|
||||
|
||||
|
||||
function Doc:select_to(...)
|
||||
local line, col, line2, col2 = self:get_selection()
|
||||
line, col = self:position_offset(line, col, ...)
|
||||
self:set_selection(line, col, line2, col2)
|
||||
end
|
||||
|
||||
|
||||
return Doc
|
|
@ -0,0 +1,52 @@
|
|||
local search = {}
|
||||
|
||||
local default_opt = {}
|
||||
|
||||
|
||||
local function pattern_lower(str)
|
||||
if str:sub(1, 1) == "%" then
|
||||
return str
|
||||
end
|
||||
return str:lower()
|
||||
end
|
||||
|
||||
|
||||
local function init_args(doc, line, col, text, opt)
|
||||
opt = opt or default_opt
|
||||
line, col = doc:sanitize_position(line, col)
|
||||
|
||||
if opt.no_case then
|
||||
if opt.pattern then
|
||||
text = text:gsub("%%?.", pattern_lower)
|
||||
else
|
||||
text = text:lower()
|
||||
end
|
||||
end
|
||||
|
||||
return doc, line, col, text, opt
|
||||
end
|
||||
|
||||
|
||||
function search.find(doc, line, col, text, opt)
|
||||
doc, line, col, text, opt = init_args(doc, line, col, text, opt)
|
||||
|
||||
for line = line, #doc.lines do
|
||||
local line_text = doc.lines[line]
|
||||
if opt.no_case then
|
||||
line_text = line_text:lower()
|
||||
end
|
||||
local s, e = line_text:find(text, col, not opt.pattern)
|
||||
if s then
|
||||
return line, s, line, e + 1
|
||||
end
|
||||
col = 1
|
||||
end
|
||||
|
||||
if opt.wrap then
|
||||
opt = { no_case = opt.no_case, pattern = opt.pattern }
|
||||
return search.find(doc, 1, 1, text, opt)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return search
|
|
@ -0,0 +1,136 @@
|
|||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
|
||||
-- functions for translating a Doc position to another position these functions
|
||||
-- can be passed to Doc:move_to|select_to|delete_to()
|
||||
|
||||
local translate = {}
|
||||
|
||||
|
||||
local function is_non_word(char)
|
||||
return config.non_word_chars:find(char, nil, true)
|
||||
end
|
||||
|
||||
|
||||
function translate.previous_char(doc, line, col)
|
||||
repeat
|
||||
line, col = doc:position_offset(line, col, -1)
|
||||
until not common.is_utf8_cont(doc:get_char(line, col))
|
||||
return line, col
|
||||
end
|
||||
|
||||
|
||||
function translate.next_char(doc, line, col)
|
||||
repeat
|
||||
line, col = doc:position_offset(line, col, 1)
|
||||
until not common.is_utf8_cont(doc:get_char(line, col))
|
||||
return line, col
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
prev, line, col = char, l, c
|
||||
end
|
||||
return translate.start_of_word(doc, line, col)
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
line, col = doc:position_offset(line, col, 1)
|
||||
prev = char
|
||||
end
|
||||
return translate.end_of_word(doc, line, col)
|
||||
end
|
||||
|
||||
|
||||
function translate.start_of_word(doc, line, col)
|
||||
while true do
|
||||
local line2, col2 = doc:position_offset(line, col, -1)
|
||||
local char = doc:get_char(line2, col2)
|
||||
if is_non_word(char)
|
||||
or line == line2 and col == col2 then
|
||||
break
|
||||
end
|
||||
line, col = line2, col2
|
||||
end
|
||||
return line, col
|
||||
end
|
||||
|
||||
|
||||
function translate.end_of_word(doc, line, col)
|
||||
while true do
|
||||
local line2, col2 = doc:position_offset(line, col, 1)
|
||||
local char = doc:get_char(line, col)
|
||||
if is_non_word(char)
|
||||
or line == line2 and col == col2 then
|
||||
break
|
||||
end
|
||||
line, col = line2, col2
|
||||
end
|
||||
return line, col
|
||||
end
|
||||
|
||||
|
||||
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]:find("^%s*$")
|
||||
and not doc.lines[line]:find("^%s*$") then
|
||||
return line, (doc.lines[line]:find("%S"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function translate.next_block_end(doc, line, col)
|
||||
while true do
|
||||
if line >= #doc.lines then
|
||||
return #doc.lines, 1
|
||||
end
|
||||
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
|
||||
|
||||
|
||||
function translate.start_of_line(doc, line, col)
|
||||
return line, 1
|
||||
end
|
||||
|
||||
|
||||
function translate.end_of_line(doc, line, col)
|
||||
return line, math.huge
|
||||
end
|
||||
|
||||
|
||||
function translate.start_of_doc(doc, line, col)
|
||||
return 1, 1
|
||||
end
|
||||
|
||||
|
||||
function translate.end_of_doc(doc, line, col)
|
||||
return #doc.lines, #doc.lines[#doc.lines]
|
||||
end
|
||||
|
||||
|
||||
return translate
|
|
@ -0,0 +1,383 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local translate = require "core.doc.translate"
|
||||
local View = require "core.view"
|
||||
|
||||
|
||||
local DocView = View:extend()
|
||||
|
||||
|
||||
local function move_to_line_offset(dv, line, col, offset)
|
||||
local xo = dv.last_x_offset
|
||||
if xo.line ~= line or xo.col ~= col then
|
||||
xo.offset = dv:get_col_x_offset(line, col)
|
||||
end
|
||||
xo.line = line + offset
|
||||
xo.col = dv:get_x_offset_col(line + offset, xo.offset)
|
||||
return xo.line, xo.col
|
||||
end
|
||||
|
||||
|
||||
DocView.translate = {
|
||||
["previous_page"] = function(doc, line, col, dv)
|
||||
local min, max = dv:get_visible_line_range()
|
||||
return line - (max - min), 1
|
||||
end,
|
||||
|
||||
["next_page"] = function(doc, line, col, dv)
|
||||
local min, max = dv:get_visible_line_range()
|
||||
return line + (max - min), 1
|
||||
end,
|
||||
|
||||
["previous_line"] = function(doc, line, col, dv)
|
||||
if line == 1 then
|
||||
return 1, 1
|
||||
end
|
||||
return move_to_line_offset(dv, line, col, -1)
|
||||
end,
|
||||
|
||||
["next_line"] = function(doc, line, col, dv)
|
||||
if line == #doc.lines then
|
||||
return #doc.lines, math.huge
|
||||
end
|
||||
return move_to_line_offset(dv, line, col, 1)
|
||||
end,
|
||||
}
|
||||
|
||||
|
||||
|
||||
function DocView:new(doc)
|
||||
DocView.super.new(self)
|
||||
self.cursor = "ibeam"
|
||||
self.scrollable = true
|
||||
self.doc = assert(doc)
|
||||
self.font = "code_font"
|
||||
self.last_x_offset = {}
|
||||
self.blink_timer = 0
|
||||
end
|
||||
|
||||
|
||||
function DocView:try_close(do_close)
|
||||
if self.doc:is_dirty()
|
||||
and #core.get_views_referencing_doc(self.doc) == 1 then
|
||||
core.command_view:enter("Unsaved Changes; Confirm Close", function(_, item)
|
||||
if item.text:match("^[cC]") then
|
||||
do_close()
|
||||
elseif item.text:match("^[sS]") then
|
||||
self.doc:save()
|
||||
do_close()
|
||||
end
|
||||
end, function(text)
|
||||
local items = {}
|
||||
if not text:find("^[^cC]") then table.insert(items, "Close Without Saving") end
|
||||
if not text:find("^[^sS]") then table.insert(items, "Save And Close") end
|
||||
return items
|
||||
end)
|
||||
else
|
||||
do_close()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_name()
|
||||
local post = self.doc:is_dirty() and "*" or ""
|
||||
local name = self.doc:get_name()
|
||||
return name:match("[^/%\\]*$") .. post
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_scrollable_size()
|
||||
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_font()
|
||||
return style[self.font]
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_line_height()
|
||||
return math.floor(self:get_font():get_height() * config.line_height)
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_gutter_width()
|
||||
return self:get_font():get_width(#self.doc.lines) + style.padding.x * 2
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_line_screen_position(idx)
|
||||
local x, y = self:get_content_offset()
|
||||
local lh = self:get_line_height()
|
||||
local gw = self:get_gutter_width()
|
||||
return x + gw, y + (idx-1) * lh + style.padding.y
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_line_text_y_offset()
|
||||
--< https://github.com/rxi/lite/pull/275/commits/eb9946862d1540e89d86ec2ffe78ab4962002511
|
||||
local th = self:get_font():get_height()
|
||||
return math.floor(th*config.line_height - th)
|
||||
--<
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_visible_line_range()
|
||||
local x, y, x2, y2 = self:get_content_bounds()
|
||||
local lh = self:get_line_height()
|
||||
local minline = math.max(1, math.floor(y / lh))
|
||||
local maxline = math.min(#self.doc.lines, math.floor(y2 / lh) + 1)
|
||||
return minline, maxline
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_col_x_offset(line, col)
|
||||
local text = self.doc.lines[line]
|
||||
if not text then return 0 end
|
||||
return self:get_font():get_width(text:sub(1, col - 1))
|
||||
end
|
||||
|
||||
|
||||
function DocView:get_x_offset_col(line, x)
|
||||
local text = self.doc.lines[line]
|
||||
|
||||
local xoffset, last_i, i = 0, 1, 1
|
||||
for char in common.utf8_chars(text) do
|
||||
local w = self:get_font():get_width(char)
|
||||
if xoffset >= x then
|
||||
return (xoffset - x > w / 2) and last_i or i
|
||||
end
|
||||
xoffset = xoffset + w
|
||||
last_i = i
|
||||
i = i + #char
|
||||
end
|
||||
|
||||
return #text
|
||||
end
|
||||
|
||||
|
||||
function DocView:resolve_screen_position(x, y)
|
||||
local ox, oy = self:get_line_screen_position(1)
|
||||
local line = math.floor((y - oy) / self:get_line_height()) + 1
|
||||
line = common.clamp(line, 1, #self.doc.lines)
|
||||
local col = self:get_x_offset_col(line, x - ox)
|
||||
return line, col
|
||||
end
|
||||
|
||||
|
||||
function DocView:scroll_to_line(line, ignore_if_visible, instant)
|
||||
local min, max = self:get_visible_line_range()
|
||||
if not (ignore_if_visible and line > min and line < max) then
|
||||
local lh = self:get_line_height()
|
||||
self.scroll.to.y = math.max(0, lh * (line - 1) - self.size.y / 2)
|
||||
if instant then
|
||||
self.scroll.y = self.scroll.to.y
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function DocView:scroll_to_make_visible(line, col)
|
||||
local min = self:get_line_height() * (line - 1)
|
||||
local max = self:get_line_height() * (line + 2) - self.size.y
|
||||
self.scroll.to.y = math.min(self.scroll.to.y, min)
|
||||
self.scroll.to.y = math.max(self.scroll.to.y, max)
|
||||
local gw = self:get_gutter_width()
|
||||
local xoffset = self:get_col_x_offset(line, col)
|
||||
local max = xoffset - self.size.x + gw + self.size.x / 5
|
||||
self.scroll.to.x = math.max(0, max)
|
||||
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 % 4 == 2 then
|
||||
line1, col1 = translate.start_of_word(doc, line1, col1)
|
||||
line2, col2 = translate.end_of_word(doc, line2, col2)
|
||||
elseif clicks % 4 == 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
|
||||
if keymap.modkeys["shift"] then
|
||||
if clicks % 2 == 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
|
||||
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
|
||||
|
||||
|
||||
function DocView:on_mouse_moved(x, y, ...)
|
||||
DocView.super.on_mouse_moved(self, x, y, ...)
|
||||
|
||||
if self:scrollbar_overlaps_point(x, y) or self.dragging_scrollbar then
|
||||
self.cursor = "arrow"
|
||||
else
|
||||
self.cursor = "ibeam"
|
||||
end
|
||||
|
||||
if self.mouse_selecting then
|
||||
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 = nil
|
||||
end
|
||||
|
||||
|
||||
function DocView:on_text_input(text)
|
||||
self.doc:text_input(text)
|
||||
end
|
||||
|
||||
|
||||
function DocView:update()
|
||||
-- scroll to make caret visible and reset blink timer if it moved
|
||||
local line, col = self.doc:get_selection()
|
||||
if (line ~= self.last_line or col ~= self.last_col) and self.size.x > 0 then
|
||||
if core.active_view == self then
|
||||
self:scroll_to_make_visible(line, col)
|
||||
end
|
||||
self.blink_timer = 0
|
||||
self.last_line, self.last_col = line, col
|
||||
end
|
||||
|
||||
-- update blink timer
|
||||
if self == core.active_view and not self.mouse_selecting then
|
||||
local n = config.blink_period / 2
|
||||
local prev = self.blink_timer
|
||||
self.blink_timer = (self.blink_timer + 1 / config.fps) % config.blink_period
|
||||
if (self.blink_timer > n) ~= (prev > n) then
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
|
||||
DocView.super.update(self)
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw_line_highlight(x, y)
|
||||
local lh = self:get_line_height()
|
||||
renderer.draw_rect(x, y, self.size.x, lh, style.line_highlight)
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
local tx, ty = x, y + self:get_line_text_y_offset()
|
||||
local font = self:get_font()
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw_line_body(idx, x, y)
|
||||
local line, col = self.doc:get_selection()
|
||||
|
||||
-- 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 text = self.doc.lines[idx]
|
||||
if line1 ~= idx then col1 = 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()
|
||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||
end
|
||||
|
||||
-- draw line highlight if caret is on this line
|
||||
if config.highlight_current_line and not self.doc:has_selection()
|
||||
and line == idx and core.active_view == self then
|
||||
self:draw_line_highlight(x + self.scroll.x, y)
|
||||
end
|
||||
|
||||
-- draw line's text
|
||||
self:draw_line_text(idx, x, y)
|
||||
|
||||
-- draw caret if it overlaps this line
|
||||
if line == idx and core.active_view == self
|
||||
and self.blink_timer < config.blink_period / 2
|
||||
and system.window_has_focus() then
|
||||
local lh = self:get_line_height()
|
||||
local x1 = x + self:get_col_x_offset(line, col)
|
||||
renderer.draw_rect(x1, y, style.caret_width, lh, style.caret)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw_line_gutter(idx, x, y)
|
||||
local color = style.line_number
|
||||
local line1, _, line2, _ = self.doc:get_selection(true)
|
||||
if idx >= line1 and idx <= line2 then
|
||||
color = style.line_number2
|
||||
end
|
||||
local yoffset = self:get_line_text_y_offset()
|
||||
x = x + style.padding.x
|
||||
renderer.draw_text(self:get_font(), idx, x, y + yoffset, color)
|
||||
end
|
||||
|
||||
|
||||
function DocView:draw()
|
||||
self:draw_background(style.background)
|
||||
|
||||
local font = self:get_font()
|
||||
font:set_tab_width(font:get_width(" ") * config.indent_size)
|
||||
|
||||
local minline, maxline = self:get_visible_line_range()
|
||||
local lh = self:get_line_height()
|
||||
|
||||
local _, y = self:get_line_screen_position(minline)
|
||||
local x = self.position.x
|
||||
for i = minline, maxline do
|
||||
self:draw_line_gutter(i, x, y)
|
||||
y = y + lh
|
||||
end
|
||||
|
||||
local x, y = self:get_line_screen_position(minline)
|
||||
local gw = self:get_gutter_width()
|
||||
local pos = self.position
|
||||
core.push_clip_rect(pos.x + gw, pos.y, self.size.x, self.size.y)
|
||||
for i = minline, maxline do
|
||||
self:draw_line_body(i, x, y)
|
||||
y = y + lh
|
||||
end
|
||||
core.pop_clip_rect()
|
||||
|
||||
self:draw_scrollbar()
|
||||
end
|
||||
|
||||
|
||||
return DocView
|
|
@ -0,0 +1,511 @@
|
|||
require "core.strict"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local command
|
||||
local keymap
|
||||
local RootView
|
||||
local StatusView
|
||||
local CommandView
|
||||
local Doc
|
||||
|
||||
local core = {}
|
||||
|
||||
|
||||
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].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 {}
|
||||
local size_limit = config.file_size_limit * 10e5
|
||||
local all = system.list_dir(path) or {}
|
||||
local dirs, files = {}, {}
|
||||
|
||||
for _, file in ipairs(all) do
|
||||
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
|
||||
info.filename = file
|
||||
--< https://github.com/0xd61/lite/blob/e5e7a2dc8c3e7d3f7f92ea5124bc111126ccfa1f/data/core/init.lua#L48-L68
|
||||
local _, depth1 = file:gsub("/","")
|
||||
local _, depth2 = file:gsub("\\","")
|
||||
if (depth1+depth2) < config.project_scan_depth or config.project_scan_depth == 0 then
|
||||
table.insert(info.type == "dir" and dirs or files, info)
|
||||
end
|
||||
end
|
||||
if (#dirs + #files) >= config.project_max_files_per_folder then break end
|
||||
--<
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(dirs, compare_file)
|
||||
for _, f in ipairs(dirs) do
|
||||
table.insert(t, f)
|
||||
get_files(f.filename, t)
|
||||
end
|
||||
|
||||
table.sort(files, compare_file)
|
||||
for _, f in ipairs(files) do
|
||||
table.insert(t, f)
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
while true do
|
||||
-- get project files and replace previous table if the new table is
|
||||
-- different
|
||||
local t = get_files(".")
|
||||
if diff_files(core.project_files, t) then
|
||||
core.project_files = t
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
-- wait for next scan
|
||||
coroutine.yield(config.project_scan_rate)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function core.init()
|
||||
command = require "core.command"
|
||||
keymap = require "core.keymap"
|
||||
RootView = require "core.rootview"
|
||||
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.redraw = true
|
||||
|
||||
core.root_view = RootView()
|
||||
core.command_view = CommandView()
|
||||
core.status_view = StatusView()
|
||||
|
||||
core.root_view.root_node:split("down", core.command_view, true)
|
||||
core.root_view.root_node.b:split("down", core.status_view, true)
|
||||
|
||||
core.add_thread(project_scan_thread)
|
||||
command.add_defaults()
|
||||
--< @r-lyeh
|
||||
local got_language_error = not core.load_languages()
|
||||
--<
|
||||
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 _, filename in ipairs(files) do
|
||||
core.root_view:open_doc(core.open_doc(filename))
|
||||
end
|
||||
|
||||
if got_language_error or got_plugin_error or got_user_error or got_project_error then
|
||||
command.perform("core:open-log")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local temp_uid = math.floor(system.get_time() * 1000) % 0xffffffff
|
||||
local temp_file_prefix = string.format(".lite_temp_%08x", tonumber(temp_uid)) --< @r-lyeh: tonumber()
|
||||
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
|
||||
local dirty_name
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc:is_dirty() then
|
||||
dirty_count = dirty_count + 1
|
||||
dirty_name = doc:get_name()
|
||||
end
|
||||
end
|
||||
if dirty_count > 0 then
|
||||
local text
|
||||
if dirty_count == 1 then
|
||||
text = string.format("\"%s\" has unsaved changes. Quit anyway?", dirty_name)
|
||||
else
|
||||
text = string.format("%d docs have unsaved changes. Quit anyway?", dirty_count)
|
||||
end
|
||||
local confirm = system.show_confirm_dialog("Unsaved Changes", text)
|
||||
if not confirm then return end
|
||||
end
|
||||
core.quit(true)
|
||||
end
|
||||
|
||||
|
||||
function core.load_plugins()
|
||||
local no_errors = true
|
||||
local files = system.list_dir(DATADIR .. "/data/plugins")
|
||||
for _, filename in ipairs(files) do
|
||||
local modname = "plugins." .. filename:gsub(".lua$", "")
|
||||
local ok = core.try(require, modname)
|
||||
if ok then
|
||||
core.log_quiet("Loaded plugin %q", modname)
|
||||
else
|
||||
no_errors = false
|
||||
end
|
||||
end
|
||||
return no_errors
|
||||
end
|
||||
|
||||
--< @r-lyeh
|
||||
function core.load_languages()
|
||||
local no_errors = true
|
||||
local files = system.list_dir(DATADIR .. "/data/languages")
|
||||
for _, filename in ipairs(files) do
|
||||
local modname = "languages." .. filename:gsub(".lua$", "")
|
||||
local ok = core.try(require, modname)
|
||||
if ok then
|
||||
core.log_quiet("Loaded language %q", modname)
|
||||
else
|
||||
no_errors = false
|
||||
end
|
||||
end
|
||||
return no_errors
|
||||
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
|
||||
local new = require(name)
|
||||
if type(old) == "table" then
|
||||
for k, v in pairs(new) do old[k] = v end
|
||||
package.loaded[name] = old
|
||||
end
|
||||
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
|
||||
core.threads[key] = { cr = coroutine.create(fn), wake = 0 }
|
||||
end
|
||||
|
||||
|
||||
function core.push_clip_rect(x, y, w, h)
|
||||
local x2, y2, w2, h2 = table.unpack(core.clip_rect_stack[#core.clip_rect_stack])
|
||||
local r, b, r2, b2 = x+w, y+h, x2+w2, y2+h2
|
||||
x, y = math.max(x, x2), math.max(y, y2)
|
||||
b, r = math.min(b, b2), math.min(r, r2)
|
||||
w, h = r-x, b-y
|
||||
table.insert(core.clip_rect_stack, { x, y, w, h })
|
||||
renderer.set_clip_rect(x, y, w, h)
|
||||
end
|
||||
|
||||
|
||||
function core.pop_clip_rect()
|
||||
table.remove(core.clip_rect_stack)
|
||||
local x, y, w, h = table.unpack(core.clip_rect_stack[#core.clip_rect_stack])
|
||||
renderer.set_clip_rect(x, y, w, h)
|
||||
end
|
||||
|
||||
|
||||
function core.open_doc(filename)
|
||||
if filename then
|
||||
-- try to find existing doc for filename
|
||||
local abs_filename = system.absolute_path(filename)
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc.filename
|
||||
and abs_filename == system.absolute_path(doc.filename) then
|
||||
return doc
|
||||
end
|
||||
end
|
||||
end
|
||||
-- no existing doc for filename; create new
|
||||
local doc = Doc(filename)
|
||||
table.insert(core.docs, doc)
|
||||
core.log_quiet(filename and "Opened doc \"%s\"" or "Opened new doc", filename)
|
||||
return doc
|
||||
end
|
||||
|
||||
|
||||
function core.get_views_referencing_doc(doc)
|
||||
local res = {}
|
||||
local views = core.root_view.root_node:get_children()
|
||||
for _, view in ipairs(views) do
|
||||
if view.doc == doc then table.insert(res, view) end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function log(icon, icon_color, fmt, ...)
|
||||
local text = string.format(fmt, ...)
|
||||
if icon then
|
||||
core.status_view:show_message(icon, icon_color, text)
|
||||
end
|
||||
|
||||
local info = debug.getinfo(2, "Sl")
|
||||
local at = string.format("%s:%d", info.short_src, info.currentline)
|
||||
local item = { text = text, time = os.time(), at = at }
|
||||
table.insert(core.log_items, item)
|
||||
if #core.log_items > config.max_log_items then
|
||||
table.remove(core.log_items, 1)
|
||||
end
|
||||
return item
|
||||
end
|
||||
|
||||
|
||||
function core.log(...)
|
||||
return log("i", style.text, ...)
|
||||
end
|
||||
|
||||
|
||||
function core.log_quiet(...)
|
||||
return log(nil, nil, ...)
|
||||
end
|
||||
|
||||
|
||||
function core.error(...)
|
||||
return log("!", style.accent, ...)
|
||||
end
|
||||
|
||||
|
||||
function core.try(fn, ...)
|
||||
local err
|
||||
local ok, res = xpcall(fn, function(msg)
|
||||
local item = core.error("%s", msg)
|
||||
item.info = debug.traceback(nil, 2):gsub("\t", "")
|
||||
err = msg
|
||||
end, ...)
|
||||
if ok then
|
||||
return true, res
|
||||
end
|
||||
return false, err
|
||||
end
|
||||
|
||||
|
||||
function core.on_event(type, ...)
|
||||
local did_keymap = false
|
||||
if type == "textinput" then
|
||||
core.root_view:on_text_input(...)
|
||||
elseif type == "keypressed" then
|
||||
did_keymap = keymap.on_key_pressed(...)
|
||||
elseif type == "keyreleased" then
|
||||
keymap.on_key_released(...)
|
||||
elseif type == "mousemoved" then
|
||||
core.root_view:on_mouse_moved(...)
|
||||
elseif type == "mousepressed" then
|
||||
core.root_view:on_mouse_pressed(...)
|
||||
elseif type == "mousereleased" then
|
||||
core.root_view:on_mouse_released(...)
|
||||
elseif type == "mousewheel" then
|
||||
core.root_view:on_mouse_wheel(...)
|
||||
elseif type == "filedropped" then
|
||||
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()
|
||||
end
|
||||
return did_keymap
|
||||
end
|
||||
|
||||
|
||||
function core.step()
|
||||
-- handle events
|
||||
local did_keymap = false
|
||||
local mouse_moved = false
|
||||
local mouse = { x = 0, y = 0, dx = 0, dy = 0 }
|
||||
|
||||
for type, a,b,c,d in system.poll_event do
|
||||
if type == "mousemoved" then
|
||||
mouse_moved = true
|
||||
mouse.x, mouse.y = a, b
|
||||
mouse.dx, mouse.dy = mouse.dx + c, mouse.dy + d
|
||||
elseif type == "textinput" and did_keymap then
|
||||
did_keymap = false
|
||||
else
|
||||
local _, res = core.try(core.on_event, type, a, b, c, d)
|
||||
did_keymap = res or did_keymap
|
||||
end
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
if not core.redraw and not system.window_has_focus() then return false end --< https://github.com/rxi/lite/pull/285/commits/a38b89ca26dde1353cf1a6bcb2dc338ebc20473d
|
||||
|
||||
if mouse_moved then
|
||||
core.try(core.on_event, "mousemoved", mouse.x, mouse.y, mouse.dx, mouse.dy)
|
||||
end
|
||||
|
||||
local width, height = renderer.get_size()
|
||||
|
||||
-- update
|
||||
core.root_view.size.x, core.root_view.size.y = width, height
|
||||
core.root_view:update()
|
||||
if not core.redraw then return false end
|
||||
core.redraw = false
|
||||
|
||||
-- close unreferenced docs
|
||||
for i = #core.docs, 1, -1 do
|
||||
local doc = core.docs[i]
|
||||
if #core.get_views_referencing_doc(doc) == 0 then
|
||||
table.remove(core.docs, i)
|
||||
core.log_quiet("Closed doc \"%s\"", doc:get_name())
|
||||
end
|
||||
end
|
||||
|
||||
-- update window title
|
||||
local name = core.active_view:get_name()
|
||||
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
|
||||
renderer.begin_frame()
|
||||
core.clip_rect_stack[1] = { 0, 0, width, height }
|
||||
renderer.set_clip_rect(table.unpack(core.clip_rect_stack[1]))
|
||||
core.root_view:draw()
|
||||
renderer.end_frame()
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local run_threads = coroutine.wrap(function()
|
||||
while true do
|
||||
local max_time = 1 / config.fps - 0.004
|
||||
local ran_any_threads = false
|
||||
|
||||
for k, thread in pairs(core.threads) do
|
||||
-- run thread
|
||||
if thread.wake < system.get_time() then
|
||||
local _, wait = assert(coroutine.resume(thread.cr))
|
||||
if coroutine.status(thread.cr) == "dead" then
|
||||
if type(k) == "number" then
|
||||
table.remove(core.threads, k)
|
||||
else
|
||||
core.threads[k] = nil
|
||||
end
|
||||
elseif wait then
|
||||
thread.wake = system.get_time() + wait
|
||||
end
|
||||
ran_any_threads = true
|
||||
end
|
||||
|
||||
-- stop running threads if we're about to hit the end of frame
|
||||
if system.get_time() - core.frame_start > max_time then
|
||||
coroutine.yield()
|
||||
end
|
||||
end
|
||||
|
||||
if not ran_any_threads then coroutine.yield() end
|
||||
end
|
||||
end)
|
||||
|
||||
--< @r-lyeh { split core.run() into core.run1()
|
||||
function core.run1()
|
||||
local did_redraw = core.step()
|
||||
run_threads()
|
||||
return did_redraw
|
||||
end
|
||||
|
||||
function core.run()
|
||||
while true do
|
||||
core.frame_start = system.get_time()
|
||||
local did_redraw = core.run1()
|
||||
local elapsed = system.get_time() - core.frame_start
|
||||
system.sleep(math.max(0, 1 / config.fps - elapsed))
|
||||
end
|
||||
end
|
||||
--< @r-lyeh } split core.run() into core.run1()
|
||||
|
||||
function core.on_error(err)
|
||||
-- write error to file
|
||||
local fp = io.open(EXEDIR .. "/error.txt", "wb")
|
||||
fp:write("Error: " .. tostring(err) .. "\n")
|
||||
fp:write(debug.traceback(nil, 4))
|
||||
fp:close()
|
||||
-- save copy of all unsaved documents
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc:is_dirty() and doc.filename then
|
||||
doc:save(doc.filename .. "~")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return core
|
|
@ -0,0 +1,186 @@
|
|||
local command = require "core.command"
|
||||
local keymap = {}
|
||||
|
||||
keymap.modkeys = {}
|
||||
keymap.map = {}
|
||||
keymap.reverse_map = {}
|
||||
|
||||
local modkey_map = {
|
||||
["left ctrl"] = "ctrl",
|
||||
["right ctrl"] = "ctrl",
|
||||
["left shift"] = "shift",
|
||||
["right shift"] = "shift",
|
||||
["left alt"] = "alt",
|
||||
["right alt"] = "altgr",
|
||||
}
|
||||
|
||||
local modkeys = { "ctrl", "alt", "altgr", "shift" }
|
||||
|
||||
local function key_to_stroke(k)
|
||||
local stroke = ""
|
||||
for _, mk in ipairs(modkeys) do
|
||||
if keymap.modkeys[mk] then
|
||||
stroke = stroke .. mk .. "+"
|
||||
end
|
||||
end
|
||||
return stroke .. k
|
||||
end
|
||||
|
||||
|
||||
function keymap.add(map, overwrite)
|
||||
for stroke, commands in pairs(map) do
|
||||
if type(commands) == "string" then
|
||||
commands = { commands }
|
||||
end
|
||||
if overwrite then
|
||||
keymap.map[stroke] = commands
|
||||
else
|
||||
keymap.map[stroke] = keymap.map[stroke] or {}
|
||||
for i = #commands, 1, -1 do
|
||||
table.insert(keymap.map[stroke], 1, commands[i])
|
||||
end
|
||||
end
|
||||
for _, cmd in ipairs(commands) do
|
||||
keymap.reverse_map[cmd] = stroke
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function keymap.get_binding(cmd)
|
||||
return keymap.reverse_map[cmd]
|
||||
end
|
||||
|
||||
|
||||
function keymap.on_key_pressed(k)
|
||||
local mk = modkey_map[k]
|
||||
if mk then
|
||||
keymap.modkeys[mk] = true
|
||||
-- work-around for windows where `altgr` is treated as `ctrl+alt`
|
||||
if mk == "altgr" then
|
||||
keymap.modkeys["ctrl"] = false
|
||||
end
|
||||
else
|
||||
local stroke = key_to_stroke(k)
|
||||
local commands = keymap.map[stroke]
|
||||
if commands then
|
||||
for _, cmd in ipairs(commands) do
|
||||
local performed = command.perform(cmd)
|
||||
if performed then break end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function keymap.on_key_released(k)
|
||||
local mk = modkey_map[k]
|
||||
if mk then
|
||||
keymap.modkeys[mk] = false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
keymap.add {
|
||||
["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",
|
||||
|
||||
["alt+shift+j"] = "root:split-left",
|
||||
["alt+shift+l"] = "root:split-right",
|
||||
["alt+shift+i"] = "root:split-up",
|
||||
["alt+shift+k"] = "root:split-down",
|
||||
["alt+j"] = "root:switch-to-left",
|
||||
["alt+l"] = "root:switch-to-right",
|
||||
["alt+i"] = "root:switch-to-up",
|
||||
["alt+k"] = "root:switch-to-down",
|
||||
|
||||
["ctrl+w"] = "root:close",
|
||||
["ctrl+tab"] = "root:switch-to-next-tab",
|
||||
["ctrl+shift+tab"] = "root:switch-to-previous-tab",
|
||||
["ctrl+pageup"] = "root:move-tab-left",
|
||||
["ctrl+pagedown"] = "root:move-tab-right",
|
||||
["alt+1"] = "root:switch-to-tab-1",
|
||||
["alt+2"] = "root:switch-to-tab-2",
|
||||
["alt+3"] = "root:switch-to-tab-3",
|
||||
["alt+4"] = "root:switch-to-tab-4",
|
||||
["alt+5"] = "root:switch-to-tab-5",
|
||||
["alt+6"] = "root:switch-to-tab-6",
|
||||
["alt+7"] = "root:switch-to-tab-7",
|
||||
["alt+8"] = "root:switch-to-tab-8",
|
||||
["alt+9"] = "root:switch-to-tab-9",
|
||||
|
||||
["ctrl+f"] = "find-replace:find",
|
||||
["ctrl+r"] = "find-replace:replace",
|
||||
["f3"] = "find-replace:repeat-find",
|
||||
["shift+f3"] = "find-replace:previous-find",
|
||||
["ctrl+g"] = "doc:go-to-line",
|
||||
["ctrl+s"] = "doc:save",
|
||||
["ctrl+shift+s"] = "doc:save-as",
|
||||
|
||||
["ctrl+z"] = "doc:undo",
|
||||
["ctrl+y"] = "doc:redo",
|
||||
["ctrl+x"] = "doc:cut",
|
||||
["ctrl+c"] = "doc:copy",
|
||||
["ctrl+v"] = "doc:paste",
|
||||
["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-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"] = { "find-replace:select-next", "doc:select-word" },
|
||||
["ctrl+l"] = "doc:select-lines",
|
||||
["ctrl+/"] = "doc:toggle-line-comments",
|
||||
["ctrl+up"] = "doc:move-lines-up",
|
||||
["ctrl+down"] = "doc:move-lines-down",
|
||||
["ctrl+shift+d"] = "doc:duplicate-lines",
|
||||
["ctrl+shift+k"] = "doc:delete-lines",
|
||||
|
||||
["left"] = "doc:move-to-previous-char",
|
||||
["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-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",
|
||||
["ctrl+end"] = "doc:move-to-end-of-doc",
|
||||
["pageup"] = "doc:move-to-previous-page",
|
||||
["pagedown"] = "doc:move-to-next-page",
|
||||
|
||||
["shift+left"] = "doc:select-to-previous-char",
|
||||
["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-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",
|
||||
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
|
||||
["shift+pageup"] = "doc:select-to-previous-page",
|
||||
["shift+pagedown"] = "doc:select-to-next-page",
|
||||
}
|
||||
|
||||
return keymap
|
|
@ -0,0 +1,74 @@
|
|||
local core = require "core"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
|
||||
local LogView = View:extend()
|
||||
|
||||
|
||||
function LogView:new()
|
||||
LogView.super.new(self)
|
||||
self.last_item = core.log_items[#core.log_items]
|
||||
self.scrollable = true
|
||||
self.yoffset = 0
|
||||
end
|
||||
|
||||
|
||||
function LogView:get_name()
|
||||
return "Log"
|
||||
end
|
||||
|
||||
|
||||
function LogView:update()
|
||||
local item = core.log_items[#core.log_items]
|
||||
if self.last_item ~= item then
|
||||
self.last_item = item
|
||||
self.scroll.to.y = 0
|
||||
self.yoffset = -(style.font:get_height() + style.padding.y)
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
local ox, oy = self:get_content_offset()
|
||||
local th = style.font:get_height()
|
||||
local y = oy + style.padding.y + self.yoffset
|
||||
|
||||
for i = #core.log_items, 1, -1 do
|
||||
local x = ox + style.padding.x
|
||||
local item = core.log_items[i]
|
||||
local time = os.date(nil, item.time)
|
||||
x = renderer.draw_text(style.font, time, x, y, style.dim)
|
||||
x = x + style.padding.x
|
||||
local subx = x
|
||||
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
|
||||
subx, y = draw_text_multiline(style.font, item.info, subx, y, style.dim)
|
||||
y = y + th
|
||||
end
|
||||
y = y + style.padding.y
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return LogView
|
|
@ -0,0 +1,58 @@
|
|||
local Object = {}
|
||||
Object.__index = Object
|
||||
|
||||
|
||||
function Object:new()
|
||||
end
|
||||
|
||||
|
||||
function Object:extend()
|
||||
local cls = {}
|
||||
for k, v in pairs(self) do
|
||||
if k:find("__") == 1 then
|
||||
cls[k] = v
|
||||
end
|
||||
end
|
||||
cls.__index = cls
|
||||
cls.super = self
|
||||
setmetatable(cls, self)
|
||||
return cls
|
||||
end
|
||||
|
||||
|
||||
function Object:implement(...)
|
||||
for _, cls in pairs({...}) do
|
||||
for k, v in pairs(cls) do
|
||||
if self[k] == nil and type(v) == "function" then
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Object:is(T)
|
||||
local mt = getmetatable(self)
|
||||
while mt do
|
||||
if mt == T then
|
||||
return true
|
||||
end
|
||||
mt = getmetatable(mt)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function Object:__tostring()
|
||||
return "Object"
|
||||
end
|
||||
|
||||
|
||||
function Object:__call(...)
|
||||
local obj = setmetatable({}, self)
|
||||
obj:new(...)
|
||||
return obj
|
||||
end
|
||||
|
||||
|
||||
return Object
|
|
@ -0,0 +1,506 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local Object = require "core.object"
|
||||
local View = require "core.view"
|
||||
local DocView = require "core.docview"
|
||||
local config = require "core.config"
|
||||
|
||||
|
||||
local EmptyView = View:extend()
|
||||
|
||||
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, "edit", x, y + (dh - th) / 2, color) --< @r-lyeh lite>edit
|
||||
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:find-command" },
|
||||
{ fmt = "%s to open a file from the project", cmd = "core:find-file" },
|
||||
}
|
||||
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))
|
||||
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
|
||||
|
||||
|
||||
|
||||
local Node = Object:extend()
|
||||
|
||||
function Node:new(type)
|
||||
self.type = type or "leaf"
|
||||
self.position = { x = 0, y = 0 }
|
||||
self.size = { x = 0, y = 0 }
|
||||
self.views = {}
|
||||
self.divider = 0.5
|
||||
if self.type == "leaf" then
|
||||
self:add_view(EmptyView())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:propagate(fn, ...)
|
||||
self.a[fn](self.a, ...)
|
||||
self.b[fn](self.b, ...)
|
||||
end
|
||||
|
||||
|
||||
function Node:on_mouse_moved(x, y, ...)
|
||||
self.hovered_tab = self:get_tab_overlapping_point(x, y)
|
||||
if self.type == "leaf" then
|
||||
self.active_view:on_mouse_moved(x, y, ...)
|
||||
else
|
||||
self:propagate("on_mouse_moved", x, y, ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:on_mouse_released(...)
|
||||
if self.type == "leaf" then
|
||||
self.active_view:on_mouse_released(...)
|
||||
else
|
||||
self:propagate("on_mouse_released", ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:consume(node)
|
||||
for k, _ in pairs(self) do self[k] = nil end
|
||||
for k, v in pairs(node) do self[k] = v end
|
||||
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")
|
||||
local last_active = core.active_view
|
||||
local child = Node()
|
||||
child:consume(self)
|
||||
self:consume(Node(type))
|
||||
self.a = child
|
||||
self.b = Node()
|
||||
if view then self.b:add_view(view) end
|
||||
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
|
||||
end
|
||||
return child
|
||||
end
|
||||
|
||||
|
||||
function Node:close_active_view(root)
|
||||
local do_close = function()
|
||||
if #self.views > 1 then
|
||||
local idx = self:get_view_idx(self.active_view)
|
||||
table.remove(self.views, idx)
|
||||
self:set_active_view(self.views[idx] or self.views[#self.views])
|
||||
else
|
||||
local parent = self:get_parent_node(root)
|
||||
local is_a = (parent.a == self)
|
||||
local other = parent[is_a and "b" or "a"]
|
||||
if other:get_locked_size() then
|
||||
self.views = {}
|
||||
self:add_view(EmptyView())
|
||||
else
|
||||
parent:consume(other)
|
||||
local p = parent
|
||||
while p.type ~= "leaf" do
|
||||
p = p[is_a and "a" or "b"]
|
||||
end
|
||||
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(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
|
||||
table.insert(self.views, view)
|
||||
self:set_active_view(view)
|
||||
end
|
||||
|
||||
|
||||
function Node:set_active_view(view)
|
||||
assert(self.type == "leaf", "Tried to set active view on non-leaf node")
|
||||
self.active_view = view
|
||||
core.set_active_view(view)
|
||||
end
|
||||
|
||||
|
||||
function Node:get_view_idx(view)
|
||||
for i, v in ipairs(self.views) do
|
||||
if v == view then return i end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_node_for_view(view)
|
||||
for _, v in ipairs(self.views) do
|
||||
if v == view then return self end
|
||||
end
|
||||
if self.type ~= "leaf" then
|
||||
return self.a:get_node_for_view(view) or self.b:get_node_for_view(view)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_parent_node(root)
|
||||
if root.a == self or root.b == self then
|
||||
return root
|
||||
elseif root.type ~= "leaf" then
|
||||
return self:get_parent_node(root.a) or self:get_parent_node(root.b)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_children(t)
|
||||
t = t or {}
|
||||
for _, view in ipairs(self.views) do
|
||||
table.insert(t, view)
|
||||
end
|
||||
if self.a then self.a:get_children(t) end
|
||||
if self.b then self.b:get_children(t) end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function Node:get_divider_overlapping_point(px, py)
|
||||
if self.type ~= "leaf" then
|
||||
local p = 6
|
||||
local x, y, w, h = self:get_divider_rect()
|
||||
x, y = x - p, y - p
|
||||
w, h = w + p * 2, h + p * 2
|
||||
if px > x and py > y and px < x + w and py < y + h then
|
||||
return self
|
||||
end
|
||||
return self.a:get_divider_overlapping_point(px, py)
|
||||
or self.b:get_divider_overlapping_point(px, py)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_tab_overlapping_point(px, py)
|
||||
if #self.views == 1 then return nil end
|
||||
local x, y, w, h = self:get_tab_rect(1)
|
||||
if px >= x and py >= y and px < x + w * #self.views and py < y + h then
|
||||
return math.floor((px - x) / w) + 1
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_child_overlapping_point(x, y)
|
||||
local child
|
||||
if self.type == "leaf" then
|
||||
return self
|
||||
elseif self.type == "hsplit" then
|
||||
child = (x < self.b.position.x) and self.a or self.b
|
||||
elseif self.type == "vsplit" then
|
||||
child = (y < self.b.position.y) and self.a or self.b
|
||||
end
|
||||
return child:get_child_overlapping_point(x, y)
|
||||
end
|
||||
|
||||
|
||||
function Node:get_tab_rect(idx)
|
||||
if not config.tabs_allowed then return 0,0,0,0 end --< https://github.com/rxi/lite/issues/191
|
||||
local tw = math.min(style.tab_width, math.ceil(self.size.x / #self.views))
|
||||
local h = style.font:get_height() + style.padding.y * 2
|
||||
return self.position.x + (idx-1) * tw, self.position.y, tw, h
|
||||
end
|
||||
|
||||
|
||||
function Node:get_divider_rect()
|
||||
local x, y = self.position.x, self.position.y
|
||||
if self.type == "hsplit" then
|
||||
return x + self.a.size.x, y, style.divider_size, self.size.y
|
||||
elseif self.type == "vsplit" then
|
||||
return x, y + self.a.size.y, self.size.x, style.divider_size
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_locked_size()
|
||||
if self.type == "leaf" then
|
||||
if self.locked then
|
||||
local size = self.active_view.size
|
||||
return size.x, size.y
|
||||
end
|
||||
else
|
||||
local x1, y1 = self.a:get_locked_size()
|
||||
local x2, y2 = self.b:get_locked_size()
|
||||
if x1 and x2 then
|
||||
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
|
||||
|
||||
|
||||
local function copy_position_and_size(dst, src)
|
||||
dst.position.x, dst.position.y = src.position.x, src.position.y
|
||||
dst.size.x, dst.size.y = src.size.x, src.size.y
|
||||
end
|
||||
|
||||
|
||||
-- calculating the sizes is the same for hsplits and vsplits, except the x/y
|
||||
-- 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 and x1 < 1 or x2 and x2 < 1) and 0 or style.divider_size
|
||||
if x1 then
|
||||
n = x1 + ds
|
||||
elseif x2 then
|
||||
n = self.size[x] - x2
|
||||
else
|
||||
n = math.floor(self.size[x] * self.divider)
|
||||
end
|
||||
self.a.position[x] = self.position[x]
|
||||
self.a.position[y] = self.position[y]
|
||||
self.a.size[x] = n - ds
|
||||
self.a.size[y] = self.size[y]
|
||||
self.b.position[x] = self.position[x] + n
|
||||
self.b.position[y] = self.position[y]
|
||||
self.b.size[x] = self.size[x] - n
|
||||
self.b.size[y] = self.size[y]
|
||||
end
|
||||
|
||||
|
||||
function Node:update_layout()
|
||||
if self.type == "leaf" then
|
||||
local av = self.active_view
|
||||
if #self.views > 1 then
|
||||
local _, _, _, th = self:get_tab_rect(1)
|
||||
av.position.x, av.position.y = self.position.x, self.position.y + th
|
||||
av.size.x, av.size.y = self.size.x, self.size.y - th
|
||||
else
|
||||
copy_position_and_size(av, self)
|
||||
end
|
||||
else
|
||||
local x1, y1 = self.a:get_locked_size()
|
||||
local x2, y2 = self.b:get_locked_size()
|
||||
if self.type == "hsplit" then
|
||||
calc_split_sizes(self, "x", "y", x1, x2)
|
||||
elseif self.type == "vsplit" then
|
||||
calc_split_sizes(self, "y", "x", y1, y2)
|
||||
end
|
||||
self.a:update_layout()
|
||||
self.b:update_layout()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:update()
|
||||
if self.type == "leaf" then
|
||||
for _, view in ipairs(self.views) do
|
||||
view:update()
|
||||
end
|
||||
else
|
||||
self.a:update()
|
||||
self.b:update()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:draw_tabs()
|
||||
local x, y, _, h = self:get_tab_rect(1)
|
||||
local ds = style.divider_size
|
||||
core.push_clip_rect(x, y, self.size.x, h)
|
||||
renderer.draw_rect(x, y, self.size.x, h, style.background2)
|
||||
renderer.draw_rect(x, y + h - ds, self.size.x, ds, style.divider)
|
||||
|
||||
for i, view in ipairs(self.views) do
|
||||
local x, y, w, h = self:get_tab_rect(i)
|
||||
local text = view:get_name()
|
||||
local color = style.dim
|
||||
if view == self.active_view then
|
||||
color = style.text
|
||||
renderer.draw_rect(x, y, w, h, style.background)
|
||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
||||
end
|
||||
if i == self.hovered_tab then
|
||||
color = style.text
|
||||
end
|
||||
core.push_clip_rect(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
|
||||
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
|
||||
function Node:draw()
|
||||
if self.type == "leaf" then
|
||||
if #self.views > 1 and config.tabs_allowed then --< https://github.com/rxi/lite/issues/191
|
||||
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 + pos.x % 1, size.y + pos.y % 1)
|
||||
self.active_view:draw()
|
||||
core.pop_clip_rect()
|
||||
else
|
||||
local x, y, w, h = self:get_divider_rect()
|
||||
renderer.draw_rect(x, y, w, h, style.divider)
|
||||
self:propagate("draw")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
local RootView = View:extend()
|
||||
|
||||
function RootView:new()
|
||||
RootView.super.new(self)
|
||||
self.root_node = Node()
|
||||
self.deferred_draws = {}
|
||||
self.mouse = { x = 0, y = 0 }
|
||||
end
|
||||
|
||||
|
||||
function RootView:defer_draw(fn, ...)
|
||||
table.insert(self.deferred_draws, 1, { fn = fn, ... })
|
||||
end
|
||||
|
||||
|
||||
function RootView:get_active_node()
|
||||
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
|
||||
node:set_active_view(node.views[i])
|
||||
return view
|
||||
end
|
||||
end
|
||||
local view = DocView(doc)
|
||||
node:add_view(view)
|
||||
self.root_node:update_layout()
|
||||
view:scroll_to_line(view.doc:get_selection(), true, true)
|
||||
return view
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||
if div then
|
||||
self.dragged_divider = div
|
||||
return
|
||||
end
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
local idx = node:get_tab_overlapping_point(x, y)
|
||||
if idx then
|
||||
node:set_active_view(node.views[idx])
|
||||
if button == "middle" then
|
||||
node:close_active_view(self.root_node)
|
||||
end
|
||||
else
|
||||
core.set_active_view(node.active_view)
|
||||
node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_released(...)
|
||||
if self.dragged_divider then
|
||||
self.dragged_divider = nil
|
||||
end
|
||||
self.root_node:on_mouse_released(...)
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_moved(x, y, dx, dy)
|
||||
if self.dragged_divider then
|
||||
local node = self.dragged_divider
|
||||
if node.type == "hsplit" then
|
||||
node.divider = node.divider + dx / node.size.x
|
||||
else
|
||||
node.divider = node.divider + dy / node.size.y
|
||||
end
|
||||
node.divider = common.clamp(node.divider, 0.01, 0.99)
|
||||
return
|
||||
end
|
||||
|
||||
self.mouse.x, self.mouse.y = x, y
|
||||
self.root_node:on_mouse_moved(x, y, dx, dy)
|
||||
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||
if div then
|
||||
system.set_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||
elseif node:get_tab_overlapping_point(x, y) then
|
||||
system.set_cursor("arrow")
|
||||
else
|
||||
system.set_cursor(node.active_view.cursor)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_wheel(...)
|
||||
local x, y = self.mouse.x, self.mouse.y
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
node.active_view:on_mouse_wheel(...)
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_text_input(...)
|
||||
core.active_view:on_text_input(...)
|
||||
end
|
||||
|
||||
|
||||
function RootView:update()
|
||||
copy_position_and_size(self.root_node, self)
|
||||
self.root_node:update()
|
||||
self.root_node:update_layout()
|
||||
end
|
||||
|
||||
|
||||
function RootView:draw()
|
||||
self.root_node:draw()
|
||||
while #self.deferred_draws > 0 do
|
||||
local t = table.remove(self.deferred_draws)
|
||||
t.fn(table.unpack(t))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return RootView
|
|
@ -0,0 +1,145 @@
|
|||
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()
|
||||
|
||||
StatusView.separator = " "
|
||||
StatusView.separator2 = " | "
|
||||
|
||||
|
||||
function StatusView:new()
|
||||
StatusView.super.new(self)
|
||||
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, StatusView.separator2, style.text, text
|
||||
}
|
||||
self.message_timeout = system.get_time() + config.message_timeout
|
||||
end
|
||||
|
||||
|
||||
function StatusView:update()
|
||||
self.size.y = style.font:get_height() + style.padding.y * 2
|
||||
|
||||
if system.get_time() < self.message_timeout then
|
||||
self.scroll.to.y = self.size.y
|
||||
else
|
||||
self.scroll.to.y = 0
|
||||
end
|
||||
|
||||
StatusView.super.update(self)
|
||||
end
|
||||
|
||||
|
||||
local function draw_items(self, items, x, y, draw_fn)
|
||||
local font = style.font
|
||||
local color = style.text
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
if type(item) == "userdata" then
|
||||
font = item
|
||||
elseif type(item) == "table" then
|
||||
color = item
|
||||
else
|
||||
x = draw_fn(font, color, item, nil, x, y, 0, self.size.y)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
|
||||
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()
|
||||
|
||||
--< https://github.com/rxi/lite/issues/300
|
||||
col = common.utf8_len(dv.doc:get_text(line, 1, line, col)) + 1
|
||||
--<
|
||||
|
||||
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("%.f%%", line / #dv.doc.lines * 100), --< @r-lyeh: %d -> %.f
|
||||
}, {
|
||||
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"
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
function StatusView:draw()
|
||||
self:draw_background(style.background2)
|
||||
|
||||
if self.message then
|
||||
self:draw_items(self.message, false, self.size.y)
|
||||
end
|
||||
|
||||
local left, right = self:get_items()
|
||||
self:draw_items(left)
|
||||
self:draw_items(right, true)
|
||||
end
|
||||
|
||||
|
||||
return StatusView
|
|
@ -0,0 +1,26 @@
|
|||
local strict = {}
|
||||
strict.defined = {}
|
||||
|
||||
|
||||
-- used to define a global variable
|
||||
function global(t)
|
||||
for k, v in pairs(t) do
|
||||
strict.defined[k] = true
|
||||
rawset(_G, k, v)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function strict.__newindex(t, k, v)
|
||||
error("cannot set undefined variable: " .. k, 2)
|
||||
end
|
||||
|
||||
|
||||
function strict.__index(t, k)
|
||||
if not strict.defined[k] then
|
||||
error("cannot get undefined variable: " .. k, 2)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
setmetatable(_G, strict)
|
|
@ -0,0 +1,42 @@
|
|||
local common = require "core.common"
|
||||
local style = {}
|
||||
|
||||
style.padding = { x = common.round(14 * SCALE), y = common.round(7 * SCALE) }
|
||||
style.divider_size = common.round(1 * SCALE)
|
||||
style.scrollbar_size = common.round(4 * SCALE)
|
||||
style.caret_width = common.round(2 * SCALE)
|
||||
style.tab_width = common.round(170 * SCALE)
|
||||
|
||||
style.font = renderer.font.load(DATADIR .. "/data/fonts/font.ttf", 14 * SCALE)
|
||||
style.big_font = renderer.font.load(DATADIR .. "/data/fonts/font.ttf", 34 * SCALE)
|
||||
style.icon_font = renderer.font.load(DATADIR .. "/data/fonts/icons.ttf", 14 * SCALE)
|
||||
style.code_font = renderer.font.load(DATADIR .. "/data/fonts/monospace.ttf", 13.5 * SCALE)
|
||||
|
||||
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 "#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 "#f7c95c" }
|
||||
style.syntax["operator"] = { common.color "#93DDFA" }
|
||||
style.syntax["function"] = { common.color "#93DDFA" }
|
||||
|
||||
return style
|
|
@ -0,0 +1,30 @@
|
|||
local common = require "core.common"
|
||||
|
||||
local syntax = {}
|
||||
syntax.items = {}
|
||||
|
||||
local plain_text_syntax = { patterns = {}, symbols = {} }
|
||||
|
||||
|
||||
function syntax.add(t)
|
||||
table.insert(syntax.items, t)
|
||||
end
|
||||
|
||||
|
||||
local function find(string, field)
|
||||
for i = #syntax.items, 1, -1 do
|
||||
local t = syntax.items[i]
|
||||
if common.match_pattern(string, t[field] or {}) then
|
||||
return t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function syntax.get(filename, header)
|
||||
return find(filename, "files")
|
||||
or find(header, "headers")
|
||||
or plain_text_syntax
|
||||
end
|
||||
|
||||
|
||||
return syntax
|
|
@ -0,0 +1,112 @@
|
|||
local tokenizer = {}
|
||||
|
||||
|
||||
local function push_token(t, type, text)
|
||||
local prev_type = t[#t-1]
|
||||
local prev_text = t[#t]
|
||||
if prev_type and (prev_type == type or prev_text:find("^%s*$")) then
|
||||
t[#t-1] = type
|
||||
t[#t] = prev_text .. text
|
||||
else
|
||||
table.insert(t, type)
|
||||
table.insert(t, text)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function is_escaped(text, idx, esc)
|
||||
local byte = esc:byte()
|
||||
local count = 0
|
||||
for i = idx - 1, 1, -1 do
|
||||
if text:byte(i) ~= byte then break end
|
||||
count = count + 1
|
||||
end
|
||||
return count % 2 == 1
|
||||
end
|
||||
|
||||
|
||||
local function find_non_escaped(text, pattern, offset, esc)
|
||||
while true do
|
||||
local s, e = text:find(pattern, offset)
|
||||
if not s then break end
|
||||
if esc and is_escaped(text, s, esc) then
|
||||
offset = e + 1
|
||||
else
|
||||
return s, e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
local p = syntax.patterns[state]
|
||||
local s, e = find_non_escaped(text, p.pattern[2], i, p.pattern[3])
|
||||
|
||||
if s then
|
||||
push_token(res, p.type, text:sub(i, e))
|
||||
state = nil
|
||||
i = e + 1
|
||||
else
|
||||
push_token(res, p.type, text:sub(i))
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- find matching pattern
|
||||
local matched = false
|
||||
for n, p in ipairs(syntax.patterns) do
|
||||
local pattern = (type(p.pattern) == "table") and p.pattern[1] or p.pattern
|
||||
local s, e = text:find("^" .. pattern, i)
|
||||
|
||||
if s then
|
||||
-- matched pattern; make and add token
|
||||
local t = text:sub(s, e)
|
||||
push_token(res, syntax.symbols[t] or p.type, t)
|
||||
|
||||
-- update state if this was a start|end pattern pair
|
||||
if type(p.pattern) == "table" then
|
||||
state = n
|
||||
end
|
||||
|
||||
-- move cursor past this token
|
||||
i = e + 1
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- consume character if we didn't match
|
||||
if not matched then
|
||||
push_token(res, "normal", text:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
return res, state
|
||||
end
|
||||
|
||||
|
||||
local function iter(t, i)
|
||||
i = i + 2
|
||||
local type, text = t[i], t[i+1]
|
||||
if type then
|
||||
return i, type, text
|
||||
end
|
||||
end
|
||||
|
||||
function tokenizer.each_token(t)
|
||||
return iter, t, -1
|
||||
end
|
||||
|
||||
|
||||
return tokenizer
|
|
@ -0,0 +1,158 @@
|
|||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local common = require "core.common"
|
||||
local Object = require "core.object"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
|
||||
local View = Object:extend()
|
||||
|
||||
|
||||
function View:new()
|
||||
self.position = { x = 0, y = 0 }
|
||||
self.size = { x = 0, y = 0 }
|
||||
self.scroll = { x = 0, y = 0, to = { x = 0, y = 0 } }
|
||||
self.cursor = "arrow"
|
||||
self.scrollable = false
|
||||
end
|
||||
|
||||
|
||||
function View:move_towards(t, k, dest, rate)
|
||||
if type(t) ~= "table" then
|
||||
return self:move_towards(self, t, k, dest, rate)
|
||||
end
|
||||
local val = t[k]
|
||||
if math.abs(val - dest) < 0.5 then
|
||||
t[k] = dest
|
||||
else
|
||||
t[k] = common.lerp(val, dest, rate or 0.5)
|
||||
end
|
||||
if val ~= dest then
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function View:try_close(do_close)
|
||||
do_close()
|
||||
end
|
||||
|
||||
|
||||
function View:get_name()
|
||||
return "---"
|
||||
end
|
||||
|
||||
|
||||
function View:get_scrollable_size()
|
||||
return math.huge
|
||||
end
|
||||
|
||||
|
||||
function View:get_scrollbar_rect()
|
||||
local sz = self:get_scrollable_size()
|
||||
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)
|
||||
return
|
||||
self.position.x + self.size.x - style.scrollbar_size,
|
||||
self.position.y + self.scroll.y * (self.size.y - h) / (sz - self.size.y),
|
||||
style.scrollbar_size,
|
||||
h
|
||||
end
|
||||
|
||||
|
||||
function View:scrollbar_overlaps_point(x, y)
|
||||
local sx, sy, sw, sh = self:get_scrollbar_rect()
|
||||
return x >= sx - sw * 3 and x < sx + sw and y >= sy and y < sy + sh
|
||||
end
|
||||
|
||||
|
||||
function View:on_mouse_pressed(button, x, y, clicks)
|
||||
if self:scrollbar_overlaps_point(x, y) then
|
||||
self.dragging_scrollbar = true
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function View:on_mouse_released(button, x, y)
|
||||
self.dragging_scrollbar = false
|
||||
end
|
||||
|
||||
|
||||
function View:on_mouse_moved(x, y, dx, dy)
|
||||
if self.dragging_scrollbar then
|
||||
local delta = self:get_scrollable_size() / self.size.y * dy
|
||||
self.scroll.to.y = self.scroll.to.y + delta
|
||||
end
|
||||
self.hovered_scrollbar = self:scrollbar_overlaps_point(x, y)
|
||||
end
|
||||
|
||||
|
||||
function View:on_text_input(text)
|
||||
-- no-op
|
||||
end
|
||||
|
||||
|
||||
function View:on_mouse_wheel(y)
|
||||
if self.scrollable then
|
||||
--< https://github.com/rxi/lite/pull/169/commits/aa70d3f7e89aeab7f07f0e0dbe482c1373597a9d
|
||||
if keymap.modkeys['alt'] then
|
||||
self.scroll.to.x = self.scroll.to.x + y * -config.mouse_wheel_scroll
|
||||
else
|
||||
self.scroll.to.y = self.scroll.to.y + y * -config.mouse_wheel_scroll
|
||||
end
|
||||
--<
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function View:get_content_bounds()
|
||||
local x = self.scroll.x
|
||||
local y = self.scroll.y
|
||||
return x, y, x + self.size.x, y + self.size.y
|
||||
end
|
||||
|
||||
|
||||
function View:get_content_offset()
|
||||
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
|
||||
|
||||
|
||||
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, w + x % 1, h + y % 1, color)
|
||||
end
|
||||
|
||||
|
||||
function View:draw_scrollbar()
|
||||
local x, y, w, h = self:get_scrollbar_rect()
|
||||
local highlight = self.hovered_scrollbar or self.dragging_scrollbar
|
||||
local color = highlight and style.scrollbar2 or style.scrollbar
|
||||
renderer.draw_rect(x, y, w, h, color)
|
||||
end
|
||||
|
||||
|
||||
function View:draw()
|
||||
end
|
||||
|
||||
|
||||
return View
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,59 @@
|
|||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.c$", "%.h$", "%.inl$", "%.cpp$", "%.hpp$" },
|
||||
comment = "//",
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { "#", "[^\\]\n" }, type = "comment" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "-?0x%x+", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE]*f?", type = "number" },
|
||||
{ pattern = "-?%.?%d+f?", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
},
|
||||
symbols = {
|
||||
["if"] = "keyword",
|
||||
["then"] = "keyword",
|
||||
["else"] = "keyword",
|
||||
["elseif"] = "keyword",
|
||||
["do"] = "keyword",
|
||||
["while"] = "keyword",
|
||||
["for"] = "keyword",
|
||||
["break"] = "keyword",
|
||||
["continue"] = "keyword",
|
||||
["return"] = "keyword",
|
||||
["goto"] = "keyword",
|
||||
["struct"] = "keyword",
|
||||
["union"] = "keyword",
|
||||
["typedef"] = "keyword",
|
||||
["enum"] = "keyword",
|
||||
["extern"] = "keyword",
|
||||
["static"] = "keyword",
|
||||
["volatile"] = "keyword",
|
||||
["const"] = "keyword",
|
||||
["inline"] = "keyword",
|
||||
["switch"] = "keyword",
|
||||
["case"] = "keyword",
|
||||
["default"] = "keyword",
|
||||
["auto"] = "keyword",
|
||||
["const"] = "keyword",
|
||||
["void"] = "keyword",
|
||||
["int"] = "keyword2",
|
||||
["short"] = "keyword2",
|
||||
["long"] = "keyword2",
|
||||
["float"] = "keyword2",
|
||||
["double"] = "keyword2",
|
||||
["char"] = "keyword2",
|
||||
["unsigned"] = "keyword2",
|
||||
["bool"] = "keyword2",
|
||||
["true"] = "literal",
|
||||
["false"] = "literal",
|
||||
["NULL"] = "literal",
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.css$" },
|
||||
patterns = {
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "[%a][%w-]*%s*%f[:]", type = "keyword" },
|
||||
{ pattern = "#%x+", type = "string" },
|
||||
{ pattern = "-?%d+[%d%.]*p[xt]", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.]*deg", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.]*", type = "number" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "#[%a][%w_-]*", type = "keyword2" },
|
||||
{ pattern = "@[%a][%w_-]*", type = "keyword2" },
|
||||
{ pattern = "%.[%a][%w_-]*", type = "keyword2" },
|
||||
{ pattern = "[{}:]", type = "operator" },
|
||||
},
|
||||
symbols = {},
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.js$", "%.json$", "%.cson$" },
|
||||
comment = "//",
|
||||
patterns = {
|
||||
{ pattern = "//.-\n", type = "comment" },
|
||||
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||
--< https://github.com/rxi/lite/pull/248/commits/1820b319381255dd377fa9ae373a8cde43b076da
|
||||
{ pattern = { '/', '/', '\\' }, type = "string" },
|
||||
--<
|
||||
{ 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" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
},
|
||||
symbols = {
|
||||
["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",
|
||||
},
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
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 = "%-%-.-\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" },
|
||||
{ pattern = "[%a_][%w_]*%s*%f[(\"{]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
{ pattern = "::[%a_][%w_]*::", type = "function" },
|
||||
},
|
||||
symbols = {
|
||||
["if"] = "keyword",
|
||||
["then"] = "keyword",
|
||||
["else"] = "keyword",
|
||||
["elseif"] = "keyword",
|
||||
["end"] = "keyword",
|
||||
["do"] = "keyword",
|
||||
["function"] = "keyword",
|
||||
["repeat"] = "keyword",
|
||||
["until"] = "keyword",
|
||||
["while"] = "keyword",
|
||||
["for"] = "keyword",
|
||||
["break"] = "keyword",
|
||||
["return"] = "keyword",
|
||||
["local"] = "keyword",
|
||||
["in"] = "keyword",
|
||||
["not"] = "keyword",
|
||||
["and"] = "keyword",
|
||||
["or"] = "keyword",
|
||||
["goto"] = "keyword",
|
||||
["self"] = "keyword2",
|
||||
["true"] = "literal",
|
||||
["false"] = "literal",
|
||||
["nil"] = "literal",
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.md$", "%.markdown$" },
|
||||
patterns = {
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { "```", "```" }, type = "string" },
|
||||
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||
{ pattern = "%-%-%-+", type = "comment" },
|
||||
{ pattern = "%*%s+", type = "operator" },
|
||||
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||
{ pattern = "#.-\n", type = "keyword" },
|
||||
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||
{ pattern = "https?://%S+", type = "function" },
|
||||
},
|
||||
symbols = { },
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.py$", "%.pyw$" },
|
||||
headers = "^#!.*[ /]python",
|
||||
comment = "#",
|
||||
patterns = {
|
||||
{ pattern = { "#", "\n" }, type = "comment" },
|
||||
{ pattern = { '[ruU]?"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "[ruU]?'", "'", '\\' }, type = "string" },
|
||||
{ pattern = { '"""', '"""' }, type = "string" },
|
||||
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.eE]*", type = "number" },
|
||||
{ pattern = "-?%.?%d+", type = "number" },
|
||||
{ pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
|
||||
{ pattern = "[%a_][%w_]*%f[(]", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||
},
|
||||
symbols = {
|
||||
["class"] = "keyword",
|
||||
["finally"] = "keyword",
|
||||
["is"] = "keyword",
|
||||
["return"] = "keyword",
|
||||
["continue"] = "keyword",
|
||||
["for"] = "keyword",
|
||||
["lambda"] = "keyword",
|
||||
["try"] = "keyword",
|
||||
["def"] = "keyword",
|
||||
["from"] = "keyword",
|
||||
["nonlocal"] = "keyword",
|
||||
["while"] = "keyword",
|
||||
["and"] = "keyword",
|
||||
["global"] = "keyword",
|
||||
["not"] = "keyword",
|
||||
["with"] = "keyword",
|
||||
["as"] = "keyword",
|
||||
["elif"] = "keyword",
|
||||
["if"] = "keyword",
|
||||
["or"] = "keyword",
|
||||
["else"] = "keyword",
|
||||
["import"] = "keyword",
|
||||
["pass"] = "keyword",
|
||||
["break"] = "keyword",
|
||||
["except"] = "keyword",
|
||||
["in"] = "keyword",
|
||||
["del"] = "keyword",
|
||||
["raise"] = "keyword",
|
||||
["yield"] = "keyword",
|
||||
["assert"] = "keyword",
|
||||
["self"] = "keyword2",
|
||||
["None"] = "literal",
|
||||
["True"] = "literal",
|
||||
["False"] = "literal",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.xml$", "%.html?$" },
|
||||
headers = "<%?xml",
|
||||
patterns = {
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { '%f[^>][^<]', '%f[<]' }, type = "normal" },
|
||||
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||
{ pattern = "0x[%da-fA-F]+", type = "number" },
|
||||
{ pattern = "-?%d+[%d%.]*f?", type = "number" },
|
||||
{ pattern = "-?%.?%d+f?", type = "number" },
|
||||
{ pattern = "%f[^<]![%a_][%w_]*", type = "keyword2" },
|
||||
{ pattern = "%f[^<][%a_][%w_]*", type = "function" },
|
||||
{ pattern = "%f[^<]/[%a_][%w_]*", type = "function" },
|
||||
{ pattern = "[%a_][%w_]*", type = "keyword" },
|
||||
{ pattern = "[/<>=]", type = "operator" },
|
||||
},
|
||||
symbols = {},
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2020 rxi
|
||||
|
||||
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
|
||||
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
|
||||
SOFTWARE.
|
|
@ -0,0 +1,41 @@
|
|||
# lite
|
||||
![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 fast, implemented as simply as
|
||||
possible; easy to modify and extend, or to use without doing either.
|
||||
|
||||
## 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 `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
|
||||
so as a plugin, after which a pull request to the
|
||||
[plugins repository](https://github.com/rxi/lite-plugins) can be made. In hopes
|
||||
of remaining lightweight, pull requests adding additional functionality to the
|
||||
core will likely not be merged. Bug reports and bug fixes are welcome.
|
||||
|
||||
## License
|
||||
This project is free software; you can redistribute it and/or modify it under
|
||||
the terms of the MIT license. See [LICENSE](LICENSE) for details.
|
|
@ -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).
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
local core = require "core"
|
||||
local translate = require "core.doc.translate"
|
||||
local config = require "core.config"
|
||||
local DocView = require "core.docview"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
|
||||
config.autoinsert_map = {
|
||||
["["] = "]",
|
||||
["{"] = "}",
|
||||
["("] = ")",
|
||||
['"'] = '"',
|
||||
["'"] = "'",
|
||||
["`"] = "`",
|
||||
}
|
||||
|
||||
|
||||
local function is_closer(chr)
|
||||
for _, v in pairs(config.autoinsert_map) do
|
||||
if v == chr then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function count_char(text, chr)
|
||||
local count = 0
|
||||
for _ in text:gmatch(chr) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
|
||||
local on_text_input = DocView.on_text_input
|
||||
|
||||
function DocView:on_text_input(text)
|
||||
local mapping = config.autoinsert_map[text]
|
||||
|
||||
-- prevents plugin from operating on `CommandView`
|
||||
if getmetatable(self) ~= DocView then
|
||||
return on_text_input(self, text)
|
||||
end
|
||||
|
||||
-- wrap selection if we have a selection
|
||||
if mapping and self.doc:has_selection() then
|
||||
local l1, c1, l2, c2, swap = self.doc:get_selection(true)
|
||||
self.doc:insert(l2, c2, mapping)
|
||||
self.doc:insert(l1, c1, text)
|
||||
self.doc:set_selection(l1, c1, l2, c2 + 2, swap)
|
||||
return
|
||||
end
|
||||
|
||||
-- skip inserting closing text
|
||||
local chr = self.doc:get_char(self.doc:get_selection())
|
||||
if text == chr and is_closer(chr) then
|
||||
self.doc:move_to(1)
|
||||
return
|
||||
end
|
||||
|
||||
-- don't insert closing quote if we have a non-even number on this line
|
||||
local line = self.doc:get_selection()
|
||||
if text == mapping and count_char(self.doc.lines[line], text) % 2 == 1 then
|
||||
return on_text_input(self, text)
|
||||
end
|
||||
|
||||
-- auto insert closing bracket
|
||||
if mapping and (chr:find("%s") or is_closer(chr) and chr ~= '"') then
|
||||
on_text_input(self, text)
|
||||
on_text_input(self, mapping)
|
||||
self.doc:move_to(-1)
|
||||
return
|
||||
end
|
||||
|
||||
on_text_input(self, text)
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function predicate()
|
||||
return getmetatable(core.active_view) == DocView
|
||||
and not core.active_view.doc:has_selection()
|
||||
end
|
||||
|
||||
command.add(predicate, {
|
||||
["autoinsert:backspace"] = function()
|
||||
local doc = core.active_view.doc
|
||||
local l, c = doc:get_selection()
|
||||
local chr = doc:get_char(l, c)
|
||||
if config.autoinsert_map[doc:get_char(l, c - 1)] and is_closer(chr) then
|
||||
doc:delete_to(1)
|
||||
end
|
||||
command.perform "doc:backspace"
|
||||
end,
|
||||
|
||||
["autoinsert:delete-to-previous-word-start"] = function()
|
||||
local doc = core.active_view.doc
|
||||
local le, ce = translate.previous_word_start(doc, doc:get_selection())
|
||||
while true do
|
||||
local l, c = doc:get_selection()
|
||||
if l == le and c == ce then
|
||||
break
|
||||
end
|
||||
command.perform "autoinsert:backspace"
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["backspace"] = "autoinsert:backspace",
|
||||
["ctrl+backspace"] = "autoinsert:delete-to-previous-word-start",
|
||||
["ctrl+shift+backspace"] = "autoinsert:delete-to-previous-word-start",
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
require "plugins.reflow"
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
config.autowrap_files = { "%.md$", "%.txt$" }
|
||||
|
||||
|
||||
local on_text_input = DocView.on_text_input
|
||||
|
||||
DocView.on_text_input = function(self, ...)
|
||||
on_text_input(self, ...)
|
||||
|
||||
-- early-exit if the filename does not match a file type pattern
|
||||
local filename = self.doc.filename or ""
|
||||
local matched = false
|
||||
for _, ptn in ipairs(config.autowrap_files) do
|
||||
if filename:match(ptn) then
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not matched then return end
|
||||
|
||||
-- do automatic reflow on line if we're typing at the end of the line and have
|
||||
-- reached the line limit
|
||||
local line, col = self.doc:get_selection()
|
||||
local text = self.doc:get_text(line, 1, line, math.huge)
|
||||
if #text >= config.line_limit and col > #text then
|
||||
command.perform("doc:select-lines")
|
||||
command.perform("reflow:reflow")
|
||||
command.perform("doc:move-to-next-char")
|
||||
command.perform("doc:move-to-previous-char")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,117 @@
|
|||
local core = require "core"
|
||||
local style = require "core.style"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
local bracket_maps = {
|
||||
-- [ ] ( ) { }
|
||||
{ [91] = 93, [40] = 41, [123] = 125, step = 1 },
|
||||
-- ] [ ) ( } {
|
||||
{ [93] = 91, [41] = 40, [125] = 123, step = -1 },
|
||||
}
|
||||
|
||||
|
||||
local function get_matching_bracket(doc, line, col, line_limit, open_byte, close_byte, step)
|
||||
local end_line = line + line_limit * step
|
||||
local depth = 0
|
||||
|
||||
while line ~= end_line do
|
||||
local byte = doc.lines[line]:byte(col)
|
||||
if byte == open_byte then
|
||||
depth = depth + 1
|
||||
elseif byte == close_byte then
|
||||
depth = depth - 1
|
||||
if depth == 0 then return line, col end
|
||||
end
|
||||
|
||||
local prev_line, prev_col = line, col
|
||||
line, col = doc:position_offset(line, col, step)
|
||||
if line == prev_line and col == prev_col then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local state = {}
|
||||
|
||||
local function update_state(line_limit)
|
||||
line_limit = line_limit or math.huge
|
||||
|
||||
-- reset if we don't have a document (eg. DocView isn't focused)
|
||||
local doc = core.active_view.doc
|
||||
if not doc then
|
||||
state = {}
|
||||
return
|
||||
end
|
||||
|
||||
-- early exit if nothing has changed since the last call
|
||||
local line, col = doc:get_selection()
|
||||
local change_id = doc:get_change_id()
|
||||
if state.doc == doc and state.line == line and state.col == col
|
||||
and state.change_id == change_id and state.limit == line_limit then
|
||||
return
|
||||
end
|
||||
|
||||
-- find matching bracket if we're on a bracket
|
||||
local line2, col2
|
||||
for _, map in ipairs(bracket_maps) do
|
||||
for i = 0, -1, -1 do
|
||||
local line, col = doc:position_offset(line, col, i)
|
||||
local open = doc.lines[line]:byte(col)
|
||||
local close = map[open]
|
||||
if close then
|
||||
line2, col2 = get_matching_bracket(doc, line, col, line_limit, open, close, map.step)
|
||||
goto found
|
||||
end
|
||||
end
|
||||
end
|
||||
::found::
|
||||
|
||||
-- update
|
||||
state = {
|
||||
change_id = change_id,
|
||||
doc = doc,
|
||||
line = line,
|
||||
col = col,
|
||||
line2 = line2,
|
||||
col2 = col2,
|
||||
limit = line_limit,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local update = DocView.update
|
||||
|
||||
function DocView:update(...)
|
||||
update(self, ...)
|
||||
update_state(100)
|
||||
end
|
||||
|
||||
|
||||
local draw_line_text = DocView.draw_line_text
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
draw_line_text(self, idx, x, y)
|
||||
|
||||
if self.doc == state.doc and idx == state.line2 then
|
||||
local color = style.bracketmatch_color or style.syntax["function"]
|
||||
local x1 = x + self:get_col_x_offset(idx, state.col2)
|
||||
local x2 = x + self:get_col_x_offset(idx, state.col2 + 1)
|
||||
local h = math.ceil(1 * SCALE)
|
||||
renderer.draw_rect(x1, y + self:get_line_height() - h, x2 - x1, h, color)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["bracket-match:move-to-matching"] = function()
|
||||
update_state()
|
||||
if state.line2 then
|
||||
core.active_view.doc:set_selection(state.line2, state.col2)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add { ["ctrl+m"] = "bracket-match:move-to-matching" }
|
|
@ -0,0 +1,63 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
|
||||
|
||||
local function detect_indent(doc)
|
||||
for _, text in ipairs(doc.lines) do
|
||||
local str = text:match("^ +")
|
||||
if str then return "soft", #str end
|
||||
local str = text:match("^\t+")
|
||||
if str then return "hard" end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function update_cache(doc)
|
||||
local type, size = detect_indent(doc)
|
||||
if type then
|
||||
cache[doc] = { type = type, size = size }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local new = Doc.new
|
||||
function Doc:new(...)
|
||||
new(self, ...)
|
||||
update_cache(self)
|
||||
end
|
||||
|
||||
local clean = Doc.clean
|
||||
function Doc:clean(...)
|
||||
clean(self, ...)
|
||||
update_cache(self)
|
||||
end
|
||||
|
||||
|
||||
local function with_indent_override(doc, fn, ...)
|
||||
local c = cache[doc]
|
||||
if not c then
|
||||
return fn(...)
|
||||
end
|
||||
local type, size = config.tab_type, config.indent_size
|
||||
config.tab_type, config.indent_size = c.type, c.size or config.indent_size
|
||||
local r1, r2, r3 = fn(...)
|
||||
config.tab_type, config.indent_size = type, size
|
||||
return r1, r2, r3
|
||||
end
|
||||
|
||||
|
||||
local perform = command.perform
|
||||
function command.perform(...)
|
||||
return with_indent_override(core.active_view.doc, perform, ...)
|
||||
end
|
||||
|
||||
|
||||
local draw = DocView.draw
|
||||
function DocView:draw(...)
|
||||
return with_indent_override(self.doc, draw, self, ...)
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
local function eof_newline(doc)
|
||||
local leof,neof = #doc.lines,#doc.lines
|
||||
for i = leof,1,-1 do
|
||||
if not string.match(doc.lines[i],"^%s*$") then break end
|
||||
neof = i
|
||||
end
|
||||
local eol,_ = string.find(doc.lines[neof],"\n")
|
||||
if eol then
|
||||
doc:remove(neof,eol,math.huge,math.huge)
|
||||
return
|
||||
end
|
||||
doc:insert(neof,math.huge,"\n")
|
||||
end
|
||||
|
||||
command.add("core.docview", {
|
||||
["eof-newline:eof-newline"] = function()
|
||||
eof_newline(core.active_view.doc)
|
||||
end,
|
||||
})
|
||||
|
||||
local save = Doc.save
|
||||
Doc.save = function(self, ...)
|
||||
eof_newline(self)
|
||||
save(self, ...)
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
local style = require "core.style"
|
||||
local config = require "core.config"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
|
||||
local function get_line_spaces(doc, idx, dir)
|
||||
local text = doc.lines[idx]
|
||||
if not text then
|
||||
return 0
|
||||
end
|
||||
local s, e = text:find("^%s*")
|
||||
if e == #text then
|
||||
return get_line_spaces(doc, idx + dir, dir)
|
||||
end
|
||||
local n = 0
|
||||
for i = s, e do
|
||||
n = n + (text:byte(i) == 9 and config.indent_size or 1)
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
|
||||
local function get_line_indent_guide_spaces(doc, idx)
|
||||
if doc.lines[idx]:find("^%s*\n") then
|
||||
return math.max(
|
||||
get_line_spaces(doc, idx - 1, -1),
|
||||
get_line_spaces(doc, idx + 1, 1))
|
||||
end
|
||||
return get_line_spaces(doc, idx)
|
||||
end
|
||||
|
||||
|
||||
local draw_line_text = DocView.draw_line_text
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
local spaces = get_line_indent_guide_spaces(self.doc, idx)
|
||||
local sw = self:get_font():get_width(" ")
|
||||
local w = math.ceil(1 * SCALE)
|
||||
local h = self:get_line_height()
|
||||
for i = 0, spaces - 1, config.indent_size do
|
||||
local color = style.guide or style.selection
|
||||
renderer.draw_rect(x + sw * i, y, w, h, color)
|
||||
end
|
||||
draw_line_text(self, idx, x, y)
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
local draw = DocView.draw
|
||||
|
||||
function DocView:draw(...)
|
||||
draw(self, ...)
|
||||
|
||||
local offset = self:get_font():get_width("n") * config.line_limit
|
||||
local x = self:get_line_screen_position(1) + offset
|
||||
local y = self.position.y
|
||||
local w = math.ceil(SCALE * 1)
|
||||
local h = self.size.y
|
||||
|
||||
local color = style.guide or style.selection
|
||||
renderer.draw_rect(x, y, w, h, color)
|
||||
end
|
|
@ -0,0 +1,70 @@
|
|||
-- mod-version:3
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
local handled_events = {
|
||||
["keypressed"] = true,
|
||||
["keyreleased"] = true,
|
||||
["textinput"] = true,
|
||||
}
|
||||
|
||||
local state = "stopped"
|
||||
local event_buffer = {}
|
||||
local modkeys = {}
|
||||
|
||||
local on_event = core.on_event
|
||||
|
||||
core.on_event = function(type, ...)
|
||||
local res = on_event(type, ...)
|
||||
if state == "recording" and handled_events[type] then
|
||||
table.insert(event_buffer, { type, ... })
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function clone(t)
|
||||
local res = {}
|
||||
for k, v in pairs(t) do res[k] = v end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function predicate()
|
||||
return state ~= "playing"
|
||||
end
|
||||
|
||||
|
||||
command.add(predicate, {
|
||||
["macro:toggle-record"] = function()
|
||||
if state == "stopped" then
|
||||
state = "recording"
|
||||
event_buffer = {}
|
||||
modkeys = clone(keymap.modkeys)
|
||||
core.log("Recording macro...")
|
||||
else
|
||||
state = "stopped"
|
||||
core.log("Stopped recording macro (%d events)", #event_buffer)
|
||||
end
|
||||
end,
|
||||
|
||||
["macro:play"] = function()
|
||||
state = "playing"
|
||||
core.log("Playing macro... (%d events)", #event_buffer)
|
||||
local mk = keymap.modkeys
|
||||
keymap.modkeys = clone(modkeys)
|
||||
for _, ev in ipairs(event_buffer) do
|
||||
on_event(table.unpack(ev))
|
||||
core.root_view:update()
|
||||
end
|
||||
keymap.modkeys = mk
|
||||
state = "stopped"
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
keymap.add {
|
||||
["ctrl+shift+;"] = "macro:toggle-record",
|
||||
["ctrl+;"] = "macro:play",
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
-- Markers plugin for lite text editor
|
||||
-- original implementation by Petri Häkkinen
|
||||
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
local cache = {} -- this table contains subtables for each document, each subtable is a set of line numbers
|
||||
setmetatable(cache, {
|
||||
__mode = "k",
|
||||
__index = function(t, k)
|
||||
t[k] = {}
|
||||
return t[k]
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
local function shift_lines(doc, at, diff)
|
||||
if diff == 0 then return end
|
||||
local t = {}
|
||||
for line in pairs(cache[doc]) do
|
||||
line = line >= at and line + diff or line
|
||||
t[line] = true
|
||||
end
|
||||
cache[doc] = t
|
||||
end
|
||||
|
||||
|
||||
local raw_insert = Doc.raw_insert
|
||||
|
||||
function Doc:raw_insert(line, col, text, ...)
|
||||
raw_insert(self, line, col, text, ...)
|
||||
local line_count = 0
|
||||
for _ in text:gmatch("\n") do
|
||||
line_count = line_count + 1
|
||||
end
|
||||
shift_lines(self, line, line_count)
|
||||
end
|
||||
|
||||
|
||||
local raw_remove = Doc.raw_remove
|
||||
|
||||
function Doc:raw_remove(line1, col1, line2, col2, ...)
|
||||
raw_remove(self, line1, col1, line2, col2, ...)
|
||||
shift_lines(self, line2, line1 - line2)
|
||||
end
|
||||
|
||||
|
||||
local draw_line_gutter = DocView.draw_line_gutter
|
||||
|
||||
function DocView:draw_line_gutter(idx, x, y)
|
||||
if cache[self.doc] and cache[self.doc][idx] then
|
||||
local h = self:get_line_height()
|
||||
renderer.draw_rect(x, y, style.padding.x * 0.4, h, style.selection)
|
||||
end
|
||||
draw_line_gutter(self, idx, x, y)
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["markers:toggle-marker"] = function()
|
||||
local doc = core.active_view.doc
|
||||
local line = doc:get_selection()
|
||||
local markers = cache[doc]
|
||||
|
||||
if markers[line] then
|
||||
markers[line] = nil
|
||||
else
|
||||
markers[line] = true
|
||||
end
|
||||
end,
|
||||
|
||||
["markers:go-to-next-marker"] = function()
|
||||
local doc = core.active_view.doc
|
||||
local line = doc:get_selection()
|
||||
local markers = cache[doc]
|
||||
|
||||
local first_marker = math.huge
|
||||
local next_marker = math.huge
|
||||
for l, _ in pairs(markers) do
|
||||
if l > line and l < next_marker then
|
||||
next_marker = l
|
||||
end
|
||||
first_marker = math.min(first_marker, l)
|
||||
end
|
||||
if next_marker == math.huge then
|
||||
next_marker = first_marker
|
||||
end
|
||||
if next_marker ~= math.huge then
|
||||
doc:set_selection(next_marker, 1)
|
||||
core.active_view:scroll_to_line(next_marker, true)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
keymap.add {
|
||||
["ctrl+f2"] = "markers:toggle-marker",
|
||||
["f2"] = "markers:go-to-next-marker",
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
-- General plugin settings
|
||||
config.minimap_enabled = true
|
||||
config.minimap_width = 100
|
||||
config.minimap_instant_scroll = false
|
||||
config.minimap_syntax_highlight = true
|
||||
config.minimap_scale = 1
|
||||
config.minimap_draw_background = true
|
||||
|
||||
-- Configure size for rendering each char in the minimap
|
||||
local char_height = 1 * SCALE * config.minimap_scale
|
||||
local char_spacing = 0.8 * SCALE * config.minimap_scale
|
||||
local line_spacing = 2 * SCALE * config.minimap_scale
|
||||
|
||||
-- Overloaded since the default implementation adds a extra x3 size of hotspot for the mouse to hit the scrollbar.
|
||||
local prev_scrollbar_overlaps_point = DocView.scrollbar_overlaps_point
|
||||
DocView.scrollbar_overlaps_point = function(self, x, y)
|
||||
if not config.minimap_enabled then return prev_scrollbar_overlaps_point(self, x, y) end
|
||||
|
||||
local sx, sy, sw, sh = self:get_scrollbar_rect()
|
||||
return x >= sx and x < sx + sw and y >= sy and y < sy + sh
|
||||
end
|
||||
|
||||
-- Helper function to determine if current file is too large to be shown fully inside the minimap area.
|
||||
local function is_file_too_large(self)
|
||||
local line_count = #self.doc.lines
|
||||
local _, _, _, sh = self:get_scrollbar_rect()
|
||||
|
||||
-- check if line count is too large to fit inside the minimap area
|
||||
local max_minmap_lines = math.floor(sh / line_spacing)
|
||||
return line_count > 1 and line_count > max_minmap_lines
|
||||
end
|
||||
|
||||
-- Overloaded with an extra check if the user clicked inside the minimap to automatically scroll to that line.
|
||||
local prev_on_mouse_pressed = DocView.on_mouse_pressed
|
||||
DocView.on_mouse_pressed = function(self, button, x, y, clicks)
|
||||
if not config.minimap_enabled then return prev_on_mouse_pressed(self, button, x, y, clicks) end
|
||||
|
||||
-- check if user clicked in the minimap area and jump directly to that line
|
||||
-- unless they are actually trying to perform a drag
|
||||
local minimap_hit = self:scrollbar_overlaps_point(x, y)
|
||||
if minimap_hit then
|
||||
local line_count = #self.doc.lines
|
||||
local minimap_height = line_count * line_spacing
|
||||
|
||||
-- check if line count is too large to fit inside the minimap area
|
||||
local is_too_large = is_file_too_large(self)
|
||||
if is_too_large then
|
||||
local _, _, _, sh = self:get_scrollbar_rect()
|
||||
minimap_height = sh
|
||||
end
|
||||
|
||||
-- calc which line to jump to
|
||||
local dy = y - self.position.y
|
||||
local jump_to_line = math.floor((dy / minimap_height) * line_count) + 1
|
||||
|
||||
local _, cy, _, cy2 = self:get_content_bounds()
|
||||
local lh = self:get_line_height()
|
||||
local visible_lines_count = math.max(1, (cy2 - cy) / lh)
|
||||
local visible_lines_start = math.max(1, math.floor(cy / lh))
|
||||
|
||||
-- calc if user hit the currently visible area
|
||||
local hit_visible_area = true
|
||||
if is_too_large then
|
||||
|
||||
local visible_height = visible_lines_count * line_spacing
|
||||
local scroll_pos = (visible_lines_start-1) / (line_count - visible_lines_count - 1)
|
||||
scroll_pos = math.min(1.0, scroll_pos) -- 0..1
|
||||
local visible_y = self.position.y + scroll_pos * (minimap_height - visible_height)
|
||||
|
||||
local t = (line_count - visible_lines_start) / visible_lines_count
|
||||
if t <= 1 then
|
||||
visible_y = visible_y + visible_height * (1.0 - t)
|
||||
end
|
||||
|
||||
if y < visible_y or y > visible_y + visible_height then
|
||||
hit_visible_area = false
|
||||
end
|
||||
else
|
||||
|
||||
-- If the click is on the currently visible line numbers,
|
||||
-- ignore it since then they probably want to initiate a drag instead.
|
||||
if jump_to_line < visible_lines_start or jump_to_line > visible_lines_start + visible_lines_count then
|
||||
hit_visible_area = false
|
||||
end
|
||||
end
|
||||
|
||||
-- if user didn't click on the visible area (ie not dragging), scroll accordingly
|
||||
if not hit_visible_area then
|
||||
self:scroll_to_line(jump_to_line, false, config.minimap_instant_scroll)
|
||||
return
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return prev_on_mouse_pressed(self, button, x, y, clicks)
|
||||
end
|
||||
|
||||
-- Overloaded with pretty much the same logic as original DocView implementation,
|
||||
-- with the exception of the dragging scrollbar delta. We want it to behave a bit snappier
|
||||
-- since the "scrollbar" essentially represents the lines visible in the content view.
|
||||
local prev_on_mouse_moved = DocView.on_mouse_moved
|
||||
DocView.on_mouse_moved = function(self, x, y, dx, dy)
|
||||
if not config.minimap_enabled then return prev_on_mouse_moved(self, x, y, dx, dy) end
|
||||
|
||||
if self.dragging_scrollbar then
|
||||
local line_count = #self.doc.lines
|
||||
local lh = self:get_line_height()
|
||||
local delta = lh / line_spacing * dy
|
||||
|
||||
if is_file_too_large(self) then
|
||||
local _, sy, _, sh = self:get_scrollbar_rect()
|
||||
delta = (line_count * lh) / sh * dy
|
||||
end
|
||||
|
||||
self.scroll.to.y = self.scroll.to.y + delta
|
||||
end
|
||||
|
||||
-- we need to "hide" that the scrollbar is dragging so that View doesnt does its own scrolling logic
|
||||
local t = self.dragging_scrollbar
|
||||
self.dragging_scrollbar = false
|
||||
local r = prev_on_mouse_moved(self, x, y, dx, dy)
|
||||
self.dragging_scrollbar = t
|
||||
return r
|
||||
end
|
||||
|
||||
-- Overloaded since we want the mouse to interact with the full size of the minimap area,
|
||||
-- not juse the scrollbar.
|
||||
local prev_get_scrollbar_rect = DocView.get_scrollbar_rect
|
||||
DocView.get_scrollbar_rect = function (self)
|
||||
if not config.minimap_enabled then return prev_get_scrollbar_rect(self) end
|
||||
|
||||
return
|
||||
self.position.x + self.size.x - config.minimap_width * SCALE,
|
||||
self.position.y,
|
||||
config.minimap_width * SCALE,
|
||||
self.size.y
|
||||
end
|
||||
|
||||
-- Overloaded so we can render the minimap in the "scrollbar area".
|
||||
local prev_draw_scrollbar = DocView.draw_scrollbar
|
||||
DocView.draw_scrollbar = function (self)
|
||||
if not config.minimap_enabled then return prev_draw_scrollbar(self) end
|
||||
|
||||
local x, y, w, h = self:get_scrollbar_rect()
|
||||
|
||||
local highlight = self.hovered_scrollbar or self.dragging_scrollbar
|
||||
local visual_color = highlight and style.scrollbar2 or style.scrollbar
|
||||
|
||||
local _, cy, _, cy2 = self:get_content_bounds()
|
||||
local lh = self:get_line_height()
|
||||
local visible_lines_count = math.max(1, (cy2 - cy) / lh)
|
||||
local visible_lines_start = math.max(1, math.floor(cy / lh))
|
||||
local scroller_height = visible_lines_count * line_spacing
|
||||
local line_count = #self.doc.lines
|
||||
|
||||
local visible_y = self.position.y + (visible_lines_start-1) * line_spacing
|
||||
|
||||
|
||||
-- check if file is too large to fit inside the minimap area
|
||||
local max_minmap_lines = math.floor(h / line_spacing)
|
||||
local minimap_start_line = 1
|
||||
if is_file_too_large(self) then
|
||||
|
||||
local scroll_pos = (visible_lines_start-1) / (line_count - visible_lines_count - 1)
|
||||
scroll_pos = math.min(1.0, scroll_pos) -- 0..1, procent of visual area scrolled
|
||||
|
||||
local scroll_pos_pixels = scroll_pos * (h - scroller_height)
|
||||
visible_y = self.position.y + scroll_pos_pixels
|
||||
|
||||
-- offset visible area if user is scrolling past end
|
||||
local t = (line_count - visible_lines_start) / visible_lines_count
|
||||
if t <= 1 then
|
||||
visible_y = visible_y + scroller_height * (1.0 - t)
|
||||
end
|
||||
|
||||
minimap_start_line = visible_lines_start - math.floor(scroll_pos_pixels / line_spacing)
|
||||
minimap_start_line = math.max(1, math.min(minimap_start_line, line_count - max_minmap_lines))
|
||||
end
|
||||
|
||||
if config.minimap_draw_background then
|
||||
renderer.draw_rect(x, y, w, h, style.minimap_background or style.background)
|
||||
end
|
||||
-- draw visual rect
|
||||
renderer.draw_rect(x, visible_y, w, scroller_height, visual_color)
|
||||
|
||||
-- time to draw the actual code, setup some local vars that are used in both highlighted and plain renderind.
|
||||
local line_y = y
|
||||
|
||||
-- when not using syntax highlighted rendering, just use the normal color but dim it 50%.
|
||||
local color = style.syntax["normal"]
|
||||
color = { color[1],color[2],color[3],color[4] * 0.5 }
|
||||
|
||||
-- we try to "batch" characters so that they can be rendered as just one rectangle instead of one for each.
|
||||
local batch_width = 0
|
||||
local batch_start = x
|
||||
local minimap_cutoff_x = x + config.minimap_width * SCALE
|
||||
|
||||
-- render lines with syntax highlighting
|
||||
if config.minimap_syntax_highlight then
|
||||
|
||||
-- keep track of the highlight type, since this needs to break batches as well
|
||||
local batch_syntax_type = nil
|
||||
|
||||
local function flush_batch(type)
|
||||
if batch_width > 0 then
|
||||
-- fetch and dim colors
|
||||
color = style.syntax[batch_syntax_type]
|
||||
color = { color[1], color[2], color[3], color[4] * 0.5 }
|
||||
renderer.draw_rect(batch_start, line_y, batch_width, char_height, color)
|
||||
end
|
||||
batch_syntax_type = type
|
||||
batch_start = batch_start + batch_width
|
||||
batch_width = 0
|
||||
end
|
||||
|
||||
-- per line
|
||||
local endidx = minimap_start_line + max_minmap_lines
|
||||
endidx = math.min(endidx, line_count)
|
||||
for idx=minimap_start_line,endidx do
|
||||
batch_syntax_type = nil
|
||||
batch_start = x
|
||||
batch_width = 0
|
||||
|
||||
-- per token
|
||||
for _, type, text in self.doc.highlighter:each_token(idx) do
|
||||
-- flush prev batch
|
||||
if not batch_syntax_type then batch_syntax_type = type end
|
||||
if batch_syntax_type ~= type then
|
||||
flush_batch(type)
|
||||
end
|
||||
|
||||
-- per character
|
||||
for char in common.utf8_chars(text) do
|
||||
if char == " " or char == "\n" then
|
||||
flush_batch(type)
|
||||
batch_start = batch_start + char_spacing
|
||||
elseif batch_start + batch_width > minimap_cutoff_x then
|
||||
flush_batch(type)
|
||||
break
|
||||
else
|
||||
batch_width = batch_width + char_spacing
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
flush_batch(nil)
|
||||
line_y = line_y + line_spacing
|
||||
end
|
||||
|
||||
else -- render lines without syntax highlighting
|
||||
|
||||
local function flush_batch()
|
||||
if batch_width > 0 then
|
||||
renderer.draw_rect(batch_start, line_y, batch_width, char_height, color)
|
||||
end
|
||||
batch_start = batch_start + batch_width
|
||||
batch_width = 0
|
||||
end
|
||||
|
||||
for idx=1,line_count-1 do
|
||||
batch_start = x
|
||||
batch_width = 0
|
||||
|
||||
for char in common.utf8_chars(self.doc.lines[idx]) do
|
||||
if char == " " or char == "\n" then
|
||||
flush_batch()
|
||||
batch_start = batch_start + char_spacing
|
||||
elseif batch_start + batch_width > minimap_cutoff_x then
|
||||
flush_batch()
|
||||
else
|
||||
batch_width = batch_width + char_spacing
|
||||
end
|
||||
end
|
||||
flush_batch()
|
||||
line_y = line_y + line_spacing
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
command.add(nil, {
|
||||
["minimap:toggle-visibility"] = function()
|
||||
config.minimap_enabled = not config.minimap_enabled
|
||||
end,
|
||||
["minimap:toggle-syntax-highlighting"] = function()
|
||||
config.minimap_syntax_highlight = not config.minimap_syntax_highlight
|
||||
end,
|
||||
})
|
|
@ -0,0 +1,46 @@
|
|||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
config.motiontrail_steps = 50
|
||||
|
||||
|
||||
local function lerp(a, b, t)
|
||||
return a + (b - a) * t
|
||||
end
|
||||
|
||||
|
||||
local function get_caret_rect(dv)
|
||||
local line, col = dv.doc:get_selection()
|
||||
local x, y = dv:get_line_screen_position(line)
|
||||
x = x + dv:get_col_x_offset(line, col)
|
||||
return x, y, style.caret_width, dv:get_line_height()
|
||||
end
|
||||
|
||||
|
||||
local last_x, last_y, last_view
|
||||
|
||||
local draw = DocView.draw
|
||||
|
||||
function DocView:draw(...)
|
||||
draw(self, ...)
|
||||
if self ~= core.active_view then return end
|
||||
|
||||
local x, y, w, h = get_caret_rect(self)
|
||||
|
||||
if last_view == self and (x ~= last_x or y ~= last_y) then
|
||||
local lx = x
|
||||
for i = 0, 1, 1 / config.motiontrail_steps do
|
||||
local ix = lerp(x, last_x, i)
|
||||
local iy = lerp(y, last_y, i)
|
||||
local iw = math.max(w, math.ceil(math.abs(ix - lx)))
|
||||
renderer.draw_rect(ix, iy, iw, h, style.caret)
|
||||
lx = ix
|
||||
end
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
last_view, last_x, last_y = self, x, y
|
||||
end
|
||||
|
|
@ -0,0 +1,399 @@
|
|||
-- mod-version:3
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local keymap = require "core.keymap"
|
||||
local command = require "core.command"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
---@class plugins.projectsearch.resultsview : core.view
|
||||
local ResultsView = View:extend()
|
||||
|
||||
ResultsView.context = "session"
|
||||
|
||||
function ResultsView:new(path, text, fn)
|
||||
ResultsView.super.new(self)
|
||||
self.scrollable = true
|
||||
self.brightness = 0
|
||||
self:begin_search(path, text, fn)
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:get_name()
|
||||
return "Search Results"
|
||||
end
|
||||
|
||||
|
||||
local function find_all_matches_in_file(t, filename, fn)
|
||||
local fp = io.open(filename)
|
||||
if not fp then return t end
|
||||
local n = 1
|
||||
for line in fp:lines() do
|
||||
local s = fn(line)
|
||||
if s then
|
||||
-- Insert maximum 256 characters. If we insert more, for compiled files, which can have very long lines
|
||||
-- things tend to get sluggish. If our line is longer than 80 characters, begin to truncate the thing.
|
||||
local start_index = math.max(s - 80, 1)
|
||||
table.insert(t, { file = filename, text = (start_index > 1 and "..." or "") .. line:sub(start_index, 256 + start_index), line = n, col = s })
|
||||
core.redraw = true
|
||||
end
|
||||
if n % 100 == 0 then coroutine.yield(0) end
|
||||
n = n + 1
|
||||
core.redraw = true
|
||||
end
|
||||
fp:close()
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:begin_search(path, text, fn)
|
||||
self.search_args = { path, text, fn }
|
||||
self.results = {}
|
||||
self.last_file_idx = 1
|
||||
self.query = text
|
||||
self.searching = true
|
||||
self.selected_idx = 0
|
||||
|
||||
core.add_thread(function()
|
||||
local i = 1
|
||||
for dir_name, file in core.get_project_files() do
|
||||
if file.type == "file" and (not path or (dir_name .. "/" .. file.filename):find(path, 1, true) == 1) then
|
||||
local truncated_path = (dir_name == core.project_dir and "" or (dir_name .. PATHSEP))
|
||||
find_all_matches_in_file(self.results, truncated_path .. file.filename, fn)
|
||||
end
|
||||
self.last_file_idx = i
|
||||
i = i + 1
|
||||
end
|
||||
self.searching = false
|
||||
self.brightness = 100
|
||||
core.redraw = true
|
||||
end, self.results)
|
||||
|
||||
self.scroll.to.y = 0
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:refresh()
|
||||
self:begin_search(table.unpack(self.search_args))
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:on_mouse_moved(mx, my, ...)
|
||||
ResultsView.super.on_mouse_moved(self, mx, my, ...)
|
||||
self.selected_idx = 0
|
||||
for i, item, x,y,w,h in self:each_visible_result() do
|
||||
if mx >= x and my >= y and mx < x + w and my < y + h then
|
||||
self.selected_idx = i
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:on_mouse_pressed(...)
|
||||
local caught = ResultsView.super.on_mouse_pressed(self, ...)
|
||||
if not caught then
|
||||
return self:open_selected_result()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:open_selected_result()
|
||||
local res = self.results[self.selected_idx]
|
||||
if not res then
|
||||
return
|
||||
end
|
||||
core.try(function()
|
||||
local dv = core.root_view:open_doc(core.open_doc(res.file))
|
||||
core.root_view.root_node:update_layout()
|
||||
dv.doc:set_selection(res.line, res.col)
|
||||
dv:scroll_to_line(res.line, false, true)
|
||||
end)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:update()
|
||||
self:move_towards("brightness", 0, 0.1)
|
||||
ResultsView.super.update(self)
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:get_results_yoffset()
|
||||
return style.font:get_height() + style.padding.y * 3
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:get_line_height()
|
||||
return style.padding.y + style.font:get_height()
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:get_scrollable_size()
|
||||
return self:get_results_yoffset() + #self.results * self:get_line_height()
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:get_visible_results_range()
|
||||
local lh = self:get_line_height()
|
||||
local oy = self:get_results_yoffset()
|
||||
local min = math.max(1, math.floor((self.scroll.y - oy) / lh))
|
||||
return min, min + math.floor(self.size.y / lh) + 1
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:each_visible_result()
|
||||
return coroutine.wrap(function()
|
||||
local lh = self:get_line_height()
|
||||
local x, y = self:get_content_offset()
|
||||
local min, max = self:get_visible_results_range()
|
||||
y = y + self:get_results_yoffset() + lh * (min - 1)
|
||||
for i = min, max do
|
||||
local item = self.results[i]
|
||||
if not item then break end
|
||||
coroutine.yield(i, item, x, y, self.size.x, lh)
|
||||
y = y + lh
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:scroll_to_make_selected_visible()
|
||||
local h = self:get_line_height()
|
||||
local y = self:get_results_yoffset() + h * (self.selected_idx - 1)
|
||||
self.scroll.to.y = math.min(self.scroll.to.y, y)
|
||||
self.scroll.to.y = math.max(self.scroll.to.y, y + h - self.size.y)
|
||||
end
|
||||
|
||||
|
||||
function ResultsView:draw()
|
||||
self:draw_background(style.background)
|
||||
|
||||
-- status
|
||||
local ox, oy = self:get_content_offset()
|
||||
local x, y = ox + style.padding.x, oy + style.padding.y
|
||||
local files_number = core.project_files_number()
|
||||
local per = common.clamp(files_number and self.last_file_idx / files_number or 1, 0, 1)
|
||||
local text
|
||||
if self.searching then
|
||||
if files_number then
|
||||
text = string.format("Searching %.f%% (%d of %d files, %d matches) for %q...",
|
||||
per * 100, self.last_file_idx, files_number,
|
||||
#self.results, self.query)
|
||||
else
|
||||
text = string.format("Searching (%d files, %d matches) for %q...",
|
||||
self.last_file_idx, #self.results, self.query)
|
||||
end
|
||||
else
|
||||
text = string.format("Found %d matches for %q",
|
||||
#self.results, self.query)
|
||||
end
|
||||
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
|
||||
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
|
||||
|
||||
-- results
|
||||
local y1, y2 = self.position.y, self.position.y + self.size.y
|
||||
for i, item, x,y,w,h in self:each_visible_result() do
|
||||
local color = style.text
|
||||
if i == self.selected_idx then
|
||||
color = style.accent
|
||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||
end
|
||||
x = x + style.padding.x
|
||||
local text = string.format("%s at line %d (col %d): ", item.file, item.line, item.col)
|
||||
x = common.draw_text(style.font, style.dim, text, "left", x, y, w, h)
|
||||
x = common.draw_text(style.code_font, color, item.text, "left", x, y, w, h)
|
||||
end
|
||||
|
||||
self:draw_scrollbar()
|
||||
end
|
||||
|
||||
|
||||
---@param path string
|
||||
---@param text string
|
||||
---@param fn fun(line_text:string):...
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
local function begin_search(path, text, fn)
|
||||
if text == "" then
|
||||
core.error("Expected non-empty string")
|
||||
return
|
||||
end
|
||||
local rv = ResultsView(path, text, fn)
|
||||
core.root_view:get_active_node_default():add_view(rv)
|
||||
return rv
|
||||
end
|
||||
|
||||
|
||||
local function get_selected_text()
|
||||
local view = core.active_view
|
||||
local doc = (view and view.doc) and view.doc or nil
|
||||
if doc then
|
||||
return doc:get_text(table.unpack({ doc:get_selection() }))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function normalize_path(path)
|
||||
if not path then return nil end
|
||||
path = common.normalize_path(path)
|
||||
for i, project_dir in ipairs(core.project_directories) do
|
||||
if common.path_belongs_to(path, project_dir.name) then
|
||||
return project_dir.item.filename .. PATHSEP .. common.relative_path(project_dir.name, path)
|
||||
end
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
---@class plugins.projectsearch
|
||||
local projectsearch = {}
|
||||
|
||||
---@type plugins.projectsearch.resultsview
|
||||
projectsearch.ResultsView = ResultsView
|
||||
|
||||
---@param text string
|
||||
---@param path string
|
||||
---@param insensitive? boolean
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
function projectsearch.search_plain(text, path, insensitive)
|
||||
if insensitive then text = text:lower() end
|
||||
return begin_search(path, text, function(line_text)
|
||||
if insensitive then
|
||||
return line_text:lower():find(text, nil, true)
|
||||
else
|
||||
return line_text:find(text, nil, true)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param path string
|
||||
---@param insensitive? boolean
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
function projectsearch.search_regex(text, path, insensitive)
|
||||
local re, errmsg
|
||||
if insensitive then
|
||||
re, errmsg = regex.compile(text, "i")
|
||||
else
|
||||
re, errmsg = regex.compile(text)
|
||||
end
|
||||
if not re then core.log("%s", errmsg) return end
|
||||
return begin_search(path, text, function(line_text)
|
||||
return regex.cmatch(re, line_text)
|
||||
end)
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param path string
|
||||
---@param insensitive? boolean
|
||||
---@return plugins.projectsearch.resultsview?
|
||||
function projectsearch.search_fuzzy(text, path, insensitive)
|
||||
if insensitive then text = text:lower() end
|
||||
return begin_search(path, text, function(line_text)
|
||||
if insensitive then
|
||||
return common.fuzzy_match(line_text:lower(), text) and 1
|
||||
else
|
||||
return common.fuzzy_match(line_text, text) and 1
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["project-search:find"] = function(path)
|
||||
core.command_view:enter("Find Text In " .. (normalize_path(path) or "Project"), {
|
||||
text = get_selected_text(),
|
||||
select_text = true,
|
||||
submit = function(text)
|
||||
projectsearch.search_plain(text, path, true)
|
||||
end
|
||||
})
|
||||
end,
|
||||
|
||||
["project-search:find-regex"] = function(path)
|
||||
core.command_view:enter("Find Regex In " .. (normalize_path(path) or "Project"), {
|
||||
submit = function(text)
|
||||
projectsearch.search_regex(text, path, true)
|
||||
end
|
||||
})
|
||||
end,
|
||||
|
||||
["project-search:fuzzy-find"] = function(path)
|
||||
core.command_view:enter("Fuzzy Find Text In " .. (normalize_path(path) or "Project"), {
|
||||
text = get_selected_text(),
|
||||
select_text = true,
|
||||
submit = function(text)
|
||||
projectsearch.search_fuzzy(text, path, true)
|
||||
end
|
||||
})
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
command.add(ResultsView, {
|
||||
["project-search:select-previous"] = function()
|
||||
local view = core.active_view
|
||||
view.selected_idx = math.max(view.selected_idx - 1, 1)
|
||||
view:scroll_to_make_selected_visible()
|
||||
end,
|
||||
|
||||
["project-search:select-next"] = function()
|
||||
local view = core.active_view
|
||||
view.selected_idx = math.min(view.selected_idx + 1, #view.results)
|
||||
view:scroll_to_make_selected_visible()
|
||||
end,
|
||||
|
||||
["project-search:open-selected"] = function()
|
||||
core.active_view:open_selected_result()
|
||||
end,
|
||||
|
||||
["project-search:refresh"] = function()
|
||||
core.active_view:refresh()
|
||||
end,
|
||||
|
||||
["project-search:move-to-previous-page"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view.scroll.to.y - view.size.y
|
||||
end,
|
||||
|
||||
["project-search:move-to-next-page"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view.scroll.to.y + view.size.y
|
||||
end,
|
||||
|
||||
["project-search:move-to-start-of-doc"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = 0
|
||||
end,
|
||||
|
||||
["project-search:move-to-end-of-doc"] = function()
|
||||
local view = core.active_view
|
||||
view.scroll.to.y = view:get_scrollable_size()
|
||||
end
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["f5"] = "project-search:refresh",
|
||||
["ctrl+shift+f"] = "project-search:find",
|
||||
["up"] = "project-search:select-previous",
|
||||
["down"] = "project-search:select-next",
|
||||
["return"] = "project-search:open-selected",
|
||||
["pageup"] = "project-search:move-to-previous-page",
|
||||
["pagedown"] = "project-search:move-to-next-page",
|
||||
["ctrl+home"] = "project-search:move-to-start-of-doc",
|
||||
["ctrl+end"] = "project-search:move-to-end-of-doc",
|
||||
["home"] = "project-search:move-to-start-of-doc",
|
||||
["end"] = "project-search:move-to-end-of-doc"
|
||||
}
|
||||
|
||||
|
||||
return projectsearch
|
|
@ -0,0 +1,31 @@
|
|||
-- mod-version:3
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
|
||||
local escapes = {
|
||||
["\\"] = "\\\\",
|
||||
["\""] = "\\\"",
|
||||
["\n"] = "\\n",
|
||||
["\r"] = "\\r",
|
||||
["\t"] = "\\t",
|
||||
["\b"] = "\\b",
|
||||
}
|
||||
|
||||
local function replace(chr)
|
||||
return escapes[chr] or string.format("\\x%02x", chr:byte())
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["quote:quote"] = function(dv)
|
||||
dv.doc:replace(function(text)
|
||||
return '"' .. text:gsub("[\0-\31\\\"]", replace) .. '"'
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["ctrl+'"] = "quote:quote",
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
|
||||
local function wordwrap_text(text, limit)
|
||||
local t = {}
|
||||
local n = 0
|
||||
|
||||
for word in text:gmatch("%S+") do
|
||||
if n + #word > limit then
|
||||
table.insert(t, "\n")
|
||||
n = 0
|
||||
elseif #t > 0 then
|
||||
table.insert(t, " ")
|
||||
end
|
||||
table.insert(t, word)
|
||||
n = n + #word + 1
|
||||
end
|
||||
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["reflow:reflow"] = function()
|
||||
local doc = core.active_view.doc
|
||||
doc:replace(function(text)
|
||||
local prefix_set = "[^%w\n%[%](){}`'\"]*"
|
||||
|
||||
-- get line prefix and trailing whitespace
|
||||
local prefix1 = text:match("^\n*" .. prefix_set)
|
||||
local prefix2 = text:match("\n(" .. prefix_set .. ")", #prefix1+1)
|
||||
local trailing = text:match("%s*$")
|
||||
if not prefix2 or prefix2 == "" then
|
||||
prefix2 = prefix1
|
||||
end
|
||||
|
||||
-- strip all line prefixes and trailing whitespace
|
||||
text = text:sub(#prefix1+1, -#trailing - 1):gsub("\n" .. prefix_set, "\n")
|
||||
|
||||
-- split into blocks, wordwrap and join
|
||||
local line_limit = config.line_limit - #prefix1
|
||||
local blocks = {}
|
||||
text = text:gsub("\n\n", "\0")
|
||||
for block in text:gmatch("%Z+") do
|
||||
table.insert(blocks, wordwrap_text(block, line_limit))
|
||||
end
|
||||
text = table.concat(blocks, "\n\n")
|
||||
|
||||
-- add prefix to start of lines
|
||||
text = prefix1 .. text:gsub("\n", "\n" .. prefix2) .. trailing
|
||||
|
||||
return text
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
keymap.add {
|
||||
["ctrl+shift+q"] = "reflow:reflow"
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local RootView = require "core.rootview"
|
||||
local CommandView = require "core.commandview"
|
||||
|
||||
config.scale_mode = "code"
|
||||
config.scale_use_mousewheel = true
|
||||
|
||||
local font_cache = setmetatable({}, { __mode = "k" })
|
||||
|
||||
-- the following should be kept in sync with core.style's default font settings
|
||||
font_cache[style.font] = { DATADIR .. "/data/fonts/font.ttf", 14 * SCALE }
|
||||
font_cache[style.big_font] = { DATADIR .. "/data/fonts/font.ttf", 34 * SCALE }
|
||||
font_cache[style.icon_font] = { DATADIR .. "/data/fonts/icons.ttf", 14 * SCALE }
|
||||
font_cache[style.code_font] = { DATADIR .. "/data/fonts/monospace.ttf", 13.5 * SCALE }
|
||||
|
||||
|
||||
local load_font = renderer.font.load
|
||||
function renderer.font.load(...)
|
||||
local res = load_font(...)
|
||||
font_cache[res] = { ... }
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
local function scale_font(font, s)
|
||||
local fc = font_cache[font]
|
||||
return renderer.font.load(fc[1], fc[2] * s)
|
||||
end
|
||||
|
||||
|
||||
local current_scale = SCALE
|
||||
local default = current_scale
|
||||
|
||||
|
||||
local function get_scale() return current_scale end
|
||||
|
||||
|
||||
local function set_scale(scale)
|
||||
scale = common.clamp(scale, 0.2, 6)
|
||||
|
||||
-- save scroll positions
|
||||
local scrolls = {}
|
||||
for _, view in ipairs(core.root_view.root_node:get_children()) do
|
||||
local n = view:get_scrollable_size()
|
||||
if n ~= math.huge and not view:is(CommandView) then
|
||||
scrolls[view] = view.scroll.y / (n - view.size.y)
|
||||
end
|
||||
end
|
||||
|
||||
local s = scale / current_scale
|
||||
current_scale = scale
|
||||
|
||||
if config.scale_mode == "ui" then
|
||||
SCALE = current_scale
|
||||
|
||||
style.padding.x = style.padding.x * s
|
||||
style.padding.y = style.padding.y * s
|
||||
style.divider_size = style.divider_size * s
|
||||
style.scrollbar_size = style.scrollbar_size * s
|
||||
style.caret_width = style.caret_width * s
|
||||
style.tab_width = style.tab_width * s
|
||||
|
||||
style.big_font = scale_font(style.big_font, s)
|
||||
style.icon_font = scale_font(style.icon_font, s)
|
||||
style.font = scale_font(style.font, s)
|
||||
end
|
||||
|
||||
style.code_font = scale_font(style.code_font, s)
|
||||
|
||||
-- restore scroll positions
|
||||
for view, n in pairs(scrolls) do
|
||||
view.scroll.y = n * (view:get_scrollable_size() - view.size.y)
|
||||
view.scroll.to.y = view.scroll.y
|
||||
end
|
||||
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
|
||||
local on_mouse_wheel = RootView.on_mouse_wheel
|
||||
|
||||
function RootView:on_mouse_wheel(d, ...)
|
||||
if keymap.modkeys["ctrl"] and config.scale_use_mousewheel then
|
||||
if d < 0 then command.perform "scale:decrease" end
|
||||
if d > 0 then command.perform "scale:increase" end
|
||||
else
|
||||
return on_mouse_wheel(self, d, ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
command.add(nil, {
|
||||
["scale:reset" ] = function() set_scale(default) end,
|
||||
["scale:decrease"] = function() set_scale(current_scale * 0.9) end,
|
||||
["scale:increase"] = function() set_scale(current_scale * 1.1) end,
|
||||
})
|
||||
|
||||
keymap.add {
|
||||
["ctrl+0"] = "scale:reset",
|
||||
["ctrl+-"] = "scale:decrease",
|
||||
["ctrl++"] = "scale:increase",
|
||||
}
|
||||
|
||||
return { get_scale = get_scale, set_scale = set_scale }
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue