Compare commits
102 Commits
239126db70
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ab6639afb | ||
|
|
7a73a982d2 | ||
|
|
51f1fd1b4f | ||
|
|
930620a49e | ||
|
|
8cb1b49cd8 | ||
|
|
830be12b24 | ||
|
|
ed3be39037 | ||
|
|
33d623268a | ||
|
|
5198c11fc6 | ||
|
|
bc7c52e1ec | ||
|
|
bbf97eba2f | ||
|
|
52390a7aa8 | ||
|
|
d5d99cddf7 | ||
|
|
217659256b | ||
|
|
4d9cfcd302 | ||
|
|
d5099cee38 | ||
|
|
017b70f3e6 | ||
|
|
903159d2bd | ||
|
|
a87d2491bb | ||
|
|
70b752eccb | ||
|
|
805f5de852 | ||
|
|
c9acc31cfc | ||
|
|
2d1edd800a | ||
|
|
22a82db946 | ||
|
|
4e8987101d | ||
|
|
034ac5d452 | ||
|
|
9c34a4dd52 | ||
|
|
0c488bc313 | ||
|
|
1c25b39df0 | ||
|
|
2454370736 | ||
|
|
eec1e137b7 | ||
|
|
a9730924d7 | ||
|
|
88dbfb1a6c | ||
|
|
f3e709f07c | ||
|
|
7ae4e74f50 | ||
|
|
863f140edc | ||
|
|
3618d906e6 | ||
|
|
26691ab6d3 | ||
|
|
923131a5b6 | ||
|
|
7b47f2f6f5 | ||
|
|
f5825f050c | ||
|
|
05b6a7bb3c | ||
|
|
cc69d807e3 | ||
|
|
dc839cb3e0 | ||
|
|
ba6de21396 | ||
|
|
b75e0a8a55 | ||
|
|
9a64fd8e01 | ||
|
|
b0a0fca8ee | ||
|
|
710269a876 | ||
|
|
4a410c3c1a | ||
|
|
4fa1a49765 | ||
|
|
9614f01e20 | ||
|
|
36839df841 | ||
|
|
2b1eab54b3 | ||
|
|
8128c2932e | ||
|
|
851eac0ec2 | ||
|
|
df963b0893 | ||
|
|
84c992c574 | ||
|
|
1ce128a5d5 | ||
|
|
372287f557 | ||
|
|
8ecfc82d8f | ||
|
|
9d60b110f5 | ||
|
|
70f28de0e1 | ||
|
|
76e279920f | ||
|
|
37cb6248a5 | ||
|
|
37154a2067 | ||
|
|
e52450a3df | ||
|
|
6fc5f0538f | ||
|
|
c970c1c7d6 | ||
|
|
01488e4eca | ||
|
|
ce1fe5feb1 | ||
|
|
270109a56b | ||
|
|
663286b45c | ||
|
|
d5c3794a93 | ||
|
|
dcda334767 | ||
|
|
69c9f7a336 | ||
|
|
ca3d087aa3 | ||
|
|
d2be40a282 | ||
|
|
6c200f7764 | ||
|
|
8de20eb0e2 | ||
|
|
86c91668e1 | ||
|
|
1e4e9fbccc | ||
|
|
36180c5c90 | ||
|
|
5ab20ce8ab | ||
|
|
61fc0cd339 | ||
|
|
3864060699 | ||
|
|
db06c783a9 | ||
|
|
f33c9873d0 | ||
|
|
66483f6c99 | ||
|
|
da821cb581 | ||
|
|
f85697d037 | ||
|
|
02c45e0dfd | ||
|
|
05d5de4b7d | ||
|
|
e575569d8a | ||
|
|
4c9c0e5210 | ||
|
|
507bd57854 | ||
|
|
0dd4641044 | ||
|
|
57c179970e | ||
|
|
005a22a93d | ||
|
|
de06ea05cc | ||
|
|
5fecc74193 | ||
|
|
101fed6d7d |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,6 +6,7 @@ src/external/SDL/
|
|||||||
build/
|
build/
|
||||||
package/
|
package/
|
||||||
|
|
||||||
|
project.te
|
||||||
*.rdbg
|
*.rdbg
|
||||||
*.spall
|
*.spall
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
@@ -17,3 +18,4 @@ te_debug
|
|||||||
text_editor.js
|
text_editor.js
|
||||||
*.map
|
*.map
|
||||||
*.exe
|
*.exe
|
||||||
|
scratch
|
||||||
30
build.bat
30
build.bat
@@ -1,4 +1,13 @@
|
|||||||
@echo off
|
@echo off
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
cd /D "%~dp0"
|
||||||
|
|
||||||
|
for %%a in (%*) do set "%%~a=1"
|
||||||
|
if not "%release%"=="1" set debug=1
|
||||||
|
if "%debug%"=="1" set release=0 && echo [debug mode]
|
||||||
|
if "%release%"=="1" set debug=0 && echo [release mode]
|
||||||
|
if "%slow%"=="1" echo [slow mode]
|
||||||
|
if "%package%"=="1" echo [package zip]
|
||||||
|
|
||||||
if not exist "src\external\SDL" (
|
if not exist "src\external\SDL" (
|
||||||
pushd src\external
|
pushd src\external
|
||||||
@@ -7,21 +16,32 @@ if not exist "src\external\SDL" (
|
|||||||
git checkout release-3.4.0
|
git checkout release-3.4.0
|
||||||
cmake -S . -B build_win32_static -DCMAKE_BUILD_TYPE=Release -DSDL_STATIC=ON
|
cmake -S . -B build_win32_static -DCMAKE_BUILD_TYPE=Release -DSDL_STATIC=ON
|
||||||
pushd build_win32_static
|
pushd build_win32_static
|
||||||
msbuild SDL3.sln
|
msbuild SDL3.sln -maxCpuCount:16 -p:Configuration=Release
|
||||||
popd
|
popd
|
||||||
popd
|
popd
|
||||||
popd
|
popd
|
||||||
)
|
)
|
||||||
set sdl=..\src\external\SDL
|
set sdl=..\src\external\SDL
|
||||||
set sdllib=%sdl%\build_win32_static\Debug
|
set sdllib=%sdl%\build_win32_static\Release
|
||||||
|
|
||||||
mkdir build
|
mkdir build
|
||||||
pushd build
|
pushd build
|
||||||
|
|
||||||
set flags=/EHsc- /MD /Zi /FC /nologo /WX /W3 /wd4200 /wd4334 /diagnostics:column -DDEBUG_BUILD=1
|
set flags=/EHsc- /MT /Zi /FC /nologo /WX /W3 /wd4200 /wd4334 /diagnostics:column
|
||||||
|
if "%release%"=="1" set flags=%flags% -O2 -DDEBUG_BUILD=0
|
||||||
|
if "%slow%"=="1" set flags=%flags% -DSLOW_BUILD=1
|
||||||
|
|
||||||
set libs=%sdllib%/SDL3-static.lib %sdllib%/SDL_uclibc.lib kernel32.lib gdi32.lib user32.lib Imm32.lib ole32.lib Shell32.lib OleAut32.lib Cfgmgr32.lib Setupapi.lib Advapi32.lib version.lib winmm.lib
|
set libs=%sdllib%/SDL3-static.lib %sdllib%/SDL_uclibc.lib kernel32.lib gdi32.lib user32.lib Imm32.lib ole32.lib Shell32.lib OleAut32.lib Cfgmgr32.lib Setupapi.lib Advapi32.lib version.lib winmm.lib
|
||||||
cl %flags% ../src/text_editor/text_editor.cpp -Fe:te.exe -I%sdl%/include -I../src/external/glad -I../src/ %libs% -link /SUBSYSTEM:WINDOWS /NODEFAULTLIB:LIBCMT /NODEFAULTLIB:MSVCRTD
|
rem rc ..\data\icon.rc
|
||||||
|
cl %flags% ../src/text_editor/text_editor.cpp -Fe:te.exe -I%sdl%/include -I../src/external/glad -I../src/ %libs% -link /SUBSYSTEM:WINDOWS /NODEFAULTLIB:LIBCMT /NODEFAULTLIB:MSVCRTD ..\data\icon.res
|
||||||
|
|
||||||
|
:: small util when working on the editor using the editor
|
||||||
copy te.exe te2.exe
|
copy te.exe te2.exe
|
||||||
|
|
||||||
popd
|
popd
|
||||||
rem /fsanitize=address
|
|
||||||
|
if "%package%"=="1" (
|
||||||
|
copy build\te.exe .
|
||||||
|
"C:\Program Files\7-Zip\7z.exe" a te.zip te.exe
|
||||||
|
del te.exe
|
||||||
|
)
|
||||||
22
build.sh
22
build.sh
@@ -1,13 +1,21 @@
|
|||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
|
|
||||||
mkdir build
|
for arg in "$@"; do declare $arg='1'; done
|
||||||
|
if [ ! -v release ]; then debug=1; fi
|
||||||
|
if [ -v debug ]; then echo "[debug build]"; fi
|
||||||
|
if [ -v release ]; then echo "[release build]"; fi
|
||||||
|
if [ -v slow ]; then echo "[slow build]"; fi
|
||||||
|
|
||||||
|
mkdir -p build
|
||||||
cd build
|
cd build
|
||||||
|
|
||||||
FLAGS="-Wall -Wno-missing-braces -Wno-writable-strings -nostdlib++ -fsanitize=address -fno-exceptions -fdiagnostics-absolute-paths -g -Wno-writable-strings -I../src -DDEBUG_BUILD=1"
|
I="-I../src/external/SDL/include -I../src/external/lua/src -I../src/external/glad -I../src"
|
||||||
# clang -o metaprogram $FLAGS ../src/metaprogram/metaprogram.cpp
|
flags="-Wall -Wextra -Werror -Wformat=2 -Wundef -Wshadow -Wno-missing-field-initializers -Wno-missing-braces -Wno-writable-strings \
|
||||||
# ./metaprogram
|
-g -fdiagnostics-absolute-paths \
|
||||||
|
-nostdlib++ -fno-exceptions"
|
||||||
|
|
||||||
I="-I../src/external/SDL/include -I../src/external/lua/src -I../src/external/glad"
|
if [ -v debug ]; then flags="$flags -fsanitize=address,undefined -fno-omit-frame-pointer -DDEBUG_BUILD=1"; fi
|
||||||
clang -o te $FLAGS ../src/text_editor/text_editor.cpp $I -lSDL3 -lm -lbacktrace
|
if [ -v release ]; then flags="$flags -DDEBUG_BUILD=0 -O2"; fi
|
||||||
|
if [ -v slow ]; then flags="$flags -DSLOW_BUILD=1"; fi
|
||||||
|
|
||||||
# -v -Wl,--verbose
|
time clang -o te $flags ../src/text_editor/text_editor.cpp $I -lSDL3 -lm -lbacktrace
|
||||||
|
|||||||
BIN
data/icon.res
Normal file
BIN
data/icon.res
Normal file
Binary file not shown.
@@ -2,38 +2,16 @@
|
|||||||
! From a user (novice) point of view, how does it look like?
|
! From a user (novice) point of view, how does it look like?
|
||||||
|
|
||||||
- Buffer edit history separate from caret history (tagging history blocks with carets???)
|
- Buffer edit history separate from caret history (tagging history blocks with carets???)
|
||||||
- Search and replace
|
|
||||||
- We need regex for: [FormatCode matching, IsCode matching,
|
- We need regex for: [FormatCode matching, IsCode matching,
|
||||||
- IndentKind has issues with stuff still like cleaning whitespace etc.
|
|
||||||
- Remedybg commands integrated! (like clicking f5 and opening up the window)
|
|
||||||
- Macros
|
- Macros
|
||||||
- ctrl-e started doing no-ops again ... ??
|
|
||||||
- Window position: vscode opens in fullscreen and then remembers what position it was close in (could be a config option)
|
- Window position: vscode opens in fullscreen and then remembers what position it was close in (could be a config option)
|
||||||
- On Linux: Try to implement is_debugger_present()
|
- On Linux: Try to implement is_debugger_present()
|
||||||
- Probably shouldn't emit (Key pressed when ctrl etc. is not clicked!!)
|
|
||||||
|
|
||||||
- OnUpdate view hooks!
|
|
||||||
- OnSave buffer hooks which will execute the config on save
|
|
||||||
- Make the special view hooks also available for modification and registered but maybe under different name or something
|
|
||||||
- Maybe IPC for te.exe when it's already open and file arguments are passed it should perhaps open a buffer in current window??
|
- Maybe IPC for te.exe when it's already open and file arguments are passed it should perhaps open a buffer in current window??
|
||||||
|
- Add <<File>> <<ProjectFolder>> template strings to Open (Then remove SEtWorkdirhere)
|
||||||
Use session 4
|
|
||||||
- Add <<File>> <<WorkDir>> template strings to Open (Then remove SEtWorkdirhere)
|
|
||||||
- :Set Filename to name current buffer ??? :O and others like that!!
|
- :Set Filename to name current buffer ??? :O and others like that!!
|
||||||
|
|
||||||
- Make a fuzzy command !> grep and fuzzy over it??? (doesn't seem very useful for grep)
|
- Make a fuzzy command !> grep and fuzzy over it??? (doesn't seem very useful for grep)
|
||||||
- Make the equivalent of SearchProject but for cmds like !@git grep -n "@>"
|
- Make the equivalent of SearchOpenBuffers but for cmds like !@git grep -n "@>"
|
||||||
- Add Bool variable
|
- Add Bool variable
|
||||||
- RegisterCommand should_appear_in_listing variable
|
|
||||||
|
|
||||||
Use session 3:
|
|
||||||
- Maybe status view, commit changes (like to buffer name or line) on enter?
|
|
||||||
|
|
||||||
How to go about search/replace, opening code and other considerations
|
|
||||||
- Search and replace sign Find@>ReplaceWith
|
|
||||||
- We can use sed + find to search and replace, the automatic reopen should do the job
|
|
||||||
|
|
||||||
Use session 2
|
|
||||||
- Guide on the first page for new users with links to configs, tutorials
|
- Guide on the first page for new users with links to configs, tutorials
|
||||||
|
|
||||||
Debug session:
|
Debug session:
|
||||||
@@ -55,13 +33,9 @@ New UI Session
|
|||||||
|
|
||||||
- Try to add Tracking Allocator and rewrite the app, free all memory at the end of the app and check all is well
|
- Try to add Tracking Allocator and rewrite the app, free all memory at the end of the app and check all is well
|
||||||
|
|
||||||
backlog
|
|
||||||
FEATURE Some decl/function indexing in fuzzy format
|
FEATURE Some decl/function indexing in fuzzy format
|
||||||
ISSUE? Fix jump scroll, the query ends up the last line on screen, kind of wacky
|
|
||||||
FEATURE dump text editor state to file, restore state
|
FEATURE dump text editor state to file, restore state
|
||||||
- Search and replace
|
|
||||||
- word complete
|
- word complete
|
||||||
- Search all buffers in 10X style, incrementally searched results popping up on every key press (maybe we need coroutine library in C so this is easier?)
|
|
||||||
- escapeing multiple cursor after ctrl + d should put the cursor where it was (probably will need to swap secondary and primary cursor for new cursor
|
- escapeing multiple cursor after ctrl + d should put the cursor where it was (probably will need to swap secondary and primary cursor for new cursor
|
||||||
- draw indentation levels like in sublime (those lines) - we render chars one by one so seems relatively easy to figure out if whitespace belongs to beginning of line (make sure to add max value like 40 because of big files)
|
- draw indentation levels like in sublime (those lines) - we render chars one by one so seems relatively easy to figure out if whitespace belongs to beginning of line (make sure to add max value like 40 because of big files)
|
||||||
- code sections, visual demarkation if beginning of line has a very specific text + goto next / goto prev section hotkey!
|
- code sections, visual demarkation if beginning of line has a very specific text + goto next / goto prev section hotkey!
|
||||||
@@ -79,3 +53,21 @@ FEATURE dump text editor state to file, restore state
|
|||||||
- gap buffer
|
- gap buffer
|
||||||
- optimize rendering - command buffer, and vertice buffer instead of vertice buffer with scissor
|
- optimize rendering - command buffer, and vertice buffer instead of vertice buffer with scissor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
WORD COMPLETE
|
||||||
|
1. Tokenize the file (or maybe couple last viewed files)
|
||||||
|
2. Match the tokens (identifiers) with prefix string and produce a list
|
||||||
|
3. Remove duplicates
|
||||||
|
4. Sort by proximity to cursor
|
||||||
|
|
||||||
|
We save the iterator, it's enough for it to be global with unique for it arena
|
||||||
|
If prefix ask is the same - reuse the iterator go to next word, if there are none go to next buffer ...
|
||||||
|
else - dump old, create new
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#endif
|
#endif
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#undef Yield
|
||||||
|
|
||||||
API void *VReserve(size_t size) {
|
API void *VReserve(size_t size) {
|
||||||
void *result = (uint8_t *)VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE);
|
void *result = (uint8_t *)VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE);
|
||||||
@@ -91,6 +92,7 @@ API bool VDecommit(void *p, size_t size) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
API void *SystemAllocatorProc(void *object, int kind, void *p, size_t size) {
|
API void *SystemAllocatorProc(void *object, int kind, void *p, size_t size) {
|
||||||
|
Unused(object);
|
||||||
void *result = NULL;
|
void *result = NULL;
|
||||||
if (kind == AllocatorKind_Allocate) {
|
if (kind == AllocatorKind_Allocate) {
|
||||||
result = malloc(size);
|
result = malloc(size);
|
||||||
@@ -124,6 +126,7 @@ thread_local Array<MemoryRecord> MemoryTrackingRecord;
|
|||||||
|
|
||||||
|
|
||||||
API void *TrackingAllocatorProc(void *object, int kind, void *p, size_t size) {
|
API void *TrackingAllocatorProc(void *object, int kind, void *p, size_t size) {
|
||||||
|
Unused(object);
|
||||||
void *result = NULL;
|
void *result = NULL;
|
||||||
|
|
||||||
if (kind == AllocatorKind_Allocate) {
|
if (kind == AllocatorKind_Allocate) {
|
||||||
@@ -248,6 +251,7 @@ API void PopToPos(VirtualArena *arena, size_t pos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
API void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size) {
|
API void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size) {
|
||||||
|
Unused(p);
|
||||||
if (kind == AllocatorKind_Allocate) {
|
if (kind == AllocatorKind_Allocate) {
|
||||||
return PushSize((VirtualArena *)object, size);
|
return PushSize((VirtualArena *)object, size);
|
||||||
} else if (AllocatorKind_Deallocate) {
|
} else if (AllocatorKind_Deallocate) {
|
||||||
@@ -315,6 +319,7 @@ API void Unwind(BlockArena *arena, U8 *pos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
API void *BlockArenaAllocatorProc(void *object, int kind, void *p, size_t size) {
|
API void *BlockArenaAllocatorProc(void *object, int kind, void *p, size_t size) {
|
||||||
|
Unused(p);
|
||||||
BlockArena *arena = (BlockArena *)object;
|
BlockArena *arena = (BlockArena *)object;
|
||||||
if (kind == AllocatorKind_Allocate) {
|
if (kind == AllocatorKind_Allocate) {
|
||||||
return PushSize(arena, size);
|
return PushSize(arena, size);
|
||||||
|
|||||||
@@ -5,12 +5,8 @@ struct LocationTrace {
|
|||||||
int line;
|
int line;
|
||||||
};
|
};
|
||||||
|
|
||||||
#if DEBUG_BUILD
|
|
||||||
thread_local LocationTrace LocationTraceO;
|
thread_local LocationTrace LocationTraceO;
|
||||||
#define LOCATION_TRACE (LocationTraceO.file = __FILE__, LocationTraceO.line = __LINE__)
|
#define LOCATION_TRACE (LocationTraceO.file = __FILE__, LocationTraceO.line = __LINE__)
|
||||||
#else
|
|
||||||
#define LOCATION_TRACE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const int AllocatorKind_Allocate = 1;
|
const int AllocatorKind_Allocate = 1;
|
||||||
const int AllocatorKind_Deallocate = 2;
|
const int AllocatorKind_Deallocate = 2;
|
||||||
@@ -32,9 +28,13 @@ API Allocator GetTrackingAllocator();
|
|||||||
|
|
||||||
#define MemoryZero(x, size) memset(x, 0, size)
|
#define MemoryZero(x, size) memset(x, 0, size)
|
||||||
#define MemoryZeroStruct(x) memset(x, 0, sizeof(*x))
|
#define MemoryZeroStruct(x) memset(x, 0, sizeof(*x))
|
||||||
#define MemoryCopy(dst, src, size) memcpy(dst, src, size)
|
|
||||||
#define MemoryMove(dst, src, size) memmove(dst, src, size)
|
#define MemoryMove(dst, src, size) memmove(dst, src, size)
|
||||||
|
|
||||||
|
static inline void MemoryCopy(void *dst, void *src, size_t size) {
|
||||||
|
if (src == NULL) return;
|
||||||
|
memcpy(dst, src, size);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
// Block Arena
|
// Block Arena
|
||||||
///////////////////
|
///////////////////
|
||||||
@@ -51,6 +51,7 @@ struct BlockArena {
|
|||||||
U8 *end;
|
U8 *end;
|
||||||
BlockArenaNode *blocks;
|
BlockArenaNode *blocks;
|
||||||
Allocator allocator;
|
Allocator allocator;
|
||||||
|
int refs; // :CoroutineLeakCheck
|
||||||
|
|
||||||
operator Allocator() { return {BlockArenaAllocatorProc, this}; }
|
operator Allocator() { return {BlockArenaAllocatorProc, this}; }
|
||||||
};
|
};
|
||||||
@@ -70,6 +71,7 @@ struct VirtualArena {
|
|||||||
size_t reserve;
|
size_t reserve;
|
||||||
size_t commit;
|
size_t commit;
|
||||||
size_t align;
|
size_t align;
|
||||||
|
int refs; // :CoroutineLeakCheck
|
||||||
|
|
||||||
operator Allocator() {
|
operator Allocator() {
|
||||||
void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size);
|
void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size);
|
||||||
@@ -116,28 +118,32 @@ API VirtualArena *GetScratchEx(VirtualArena **conflicts, int conflict_count);
|
|||||||
struct Scratch {
|
struct Scratch {
|
||||||
VirtualArena *arena;
|
VirtualArena *arena;
|
||||||
U64 p;
|
U64 p;
|
||||||
Scratch() {arena = &ScratchArenas[0]; p = arena->len;}
|
Scratch() {arena = &ScratchArenas[0]; p = arena->len; arena->refs += 1;}
|
||||||
Scratch(VirtualArena *conflict) {
|
Scratch(VirtualArena *conflict) {
|
||||||
VirtualArena *conf[] = {conflict};
|
VirtualArena *conf[] = {conflict};
|
||||||
arena = GetScratchEx(conf, Lengthof(conf));
|
arena = GetScratchEx(conf, Lengthof(conf));
|
||||||
p = arena->len;
|
p = arena->len;
|
||||||
|
arena->refs += 1;
|
||||||
}
|
}
|
||||||
Scratch(VirtualArena *c1, VirtualArena *c2) {
|
Scratch(VirtualArena *c1, VirtualArena *c2) {
|
||||||
VirtualArena *conf[] = {c1, c2};
|
VirtualArena *conf[] = {c1, c2};
|
||||||
arena = GetScratchEx(conf, Lengthof(conf));
|
arena = GetScratchEx(conf, Lengthof(conf));
|
||||||
p = arena->len;
|
p = arena->len;
|
||||||
|
arena->refs += 1;
|
||||||
}
|
}
|
||||||
Scratch(Allocator conflict) {
|
Scratch(Allocator conflict) {
|
||||||
VirtualArena *conf[] = {(VirtualArena *)conflict.object};
|
VirtualArena *conf[] = {(VirtualArena *)conflict.object};
|
||||||
arena = GetScratchEx(conf, Lengthof(conf));
|
arena = GetScratchEx(conf, Lengthof(conf));
|
||||||
p = arena->len;
|
p = arena->len;
|
||||||
|
arena->refs += 1;
|
||||||
}
|
}
|
||||||
Scratch(Allocator c1, Allocator c2) {
|
Scratch(Allocator c1, Allocator c2) {
|
||||||
VirtualArena *conf[] = {(VirtualArena *)c1.object, (VirtualArena *)c2.object};
|
VirtualArena *conf[] = {(VirtualArena *)c1.object, (VirtualArena *)c2.object};
|
||||||
arena = GetScratchEx(conf, Lengthof(conf));
|
arena = GetScratchEx(conf, Lengthof(conf));
|
||||||
p = arena->len;
|
p = arena->len;
|
||||||
|
arena->refs += 1;
|
||||||
}
|
}
|
||||||
~Scratch() { SetLen(arena, p); }
|
~Scratch() { SetLen(arena, p); arena->refs -= 1; }
|
||||||
operator VirtualArena *() { return arena; }
|
operator VirtualArena *() { return arena; }
|
||||||
operator Allocator() { return *arena; }
|
operator Allocator() { return *arena; }
|
||||||
|
|
||||||
|
|||||||
@@ -211,12 +211,11 @@ void Reserve(Array<T> *arr, int64_t size) {
|
|||||||
|
|
||||||
T *new_data = AllocArray(arr->allocator, T, size);
|
T *new_data = AllocArray(arr->allocator, T, size);
|
||||||
Assert(new_data);
|
Assert(new_data);
|
||||||
memcpy(new_data, arr->data, arr->len * sizeof(T));
|
MemoryCopy(new_data, arr->data, arr->len * sizeof(T));
|
||||||
Dealloc(arr->allocator, arr->data);
|
if (arr->data) Dealloc(arr->allocator, arr->data);
|
||||||
|
|
||||||
arr->data = new_data;
|
arr->data = new_data;
|
||||||
arr->cap = size;
|
arr->cap = size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@@ -310,6 +309,16 @@ bool Contains(Array<T> &arr, T item) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
int64_t FindValueIndex(Array<T> &arr, T item) {
|
||||||
|
for (int64_t i = 0; i < arr.len; i += 1) {
|
||||||
|
if (arr.data[i] == item) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
int64_t GetIndex(Array<T> &arr, const T &item) {
|
int64_t GetIndex(Array<T> &arr, const T &item) {
|
||||||
ptrdiff_t index = (ptrdiff_t)(&item - arr.data);
|
ptrdiff_t index = (ptrdiff_t)(&item - arr.data);
|
||||||
@@ -483,16 +492,16 @@ struct ReverseIter {
|
|||||||
friend bool operator==(const ReverseIter &a, const ReverseIter &b) { return a.data == b.data; };
|
friend bool operator==(const ReverseIter &a, const ReverseIter &b) { return a.data == b.data; };
|
||||||
friend bool operator!=(const ReverseIter &a, const ReverseIter &b) { return a.data != b.data; };
|
friend bool operator!=(const ReverseIter &a, const ReverseIter &b) { return a.data != b.data; };
|
||||||
|
|
||||||
ReverseIter begin() { return ReverseIter{arr->end() - 1, arr}; }
|
ReverseIter begin() { return ReverseIter{arr->end() ? arr->end() - 1 : NULL, arr}; }
|
||||||
ReverseIter end() { return ReverseIter{arr->begin() - 1, arr}; }
|
ReverseIter end() { return ReverseIter{arr->begin() ? arr->begin() - 1 : NULL, arr}; }
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
ReverseIter<T> IterateInReverse(Array<T> *arr) {
|
ReverseIter<T> IterateInReverse(Array<T> *arr) {
|
||||||
return {arr->end() - 1, &arr->slice};
|
return {arr->end() ? arr->end() - 1 : NULL, &arr->slice};
|
||||||
}
|
}
|
||||||
template <class T>
|
template <class T>
|
||||||
ReverseIter<T> IterateInReverse(Slice<T> *slice) {
|
ReverseIter<T> IterateInReverse(Slice<T> *slice) {
|
||||||
return {slice->end() - 1, slice};
|
return {slice->end ? slice->end() - 1 : NULL, slice};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
|
||||||
#if defined(__APPLE__) && defined(__MACH__)
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
#define OS_POSIX 1
|
#define OS_POSIX 1
|
||||||
#define OS_MAC 1
|
#define OS_MAC 1
|
||||||
@@ -70,12 +69,30 @@
|
|||||||
#define DEBUG_BUILD 1
|
#define DEBUG_BUILD 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef SLOW_BUILD
|
||||||
|
#define SLOW_BUILD 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if SLOW_BUILD
|
||||||
|
#define IF_SLOW_BUILD(x) x
|
||||||
|
#else
|
||||||
|
#define IF_SLOW_BUILD(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
#if DEBUG_BUILD
|
#if DEBUG_BUILD
|
||||||
#define IF_DEBUG(x) x
|
#define IF_DEBUG(x) x
|
||||||
#else
|
#else
|
||||||
#define IF_DEBUG(x)
|
#define IF_DEBUG(x)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if OS_WINDOWS
|
||||||
|
#define IF_OS_WINDOWS_ELSE(x, y) x
|
||||||
|
#define IF_OS_WINDOWS(x) x
|
||||||
|
#else
|
||||||
|
#define IF_OS_WINDOWS_ELSE(x, y) y
|
||||||
|
#define IF_OS_WINDOWS(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
#ifndef NOMINMAX
|
#ifndef NOMINMAX
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
@@ -84,6 +101,7 @@
|
|||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#endif
|
#endif
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#undef Yield
|
||||||
#define BREAK() if (IsDebuggerPresent()) {__debugbreak();}
|
#define BREAK() if (IsDebuggerPresent()) {__debugbreak();}
|
||||||
#elif OS_LINUX
|
#elif OS_LINUX
|
||||||
#define BREAK() raise(SIGTRAP)
|
#define BREAK() raise(SIGTRAP)
|
||||||
@@ -107,8 +125,9 @@ EM_JS(void, JS_Breakpoint, (), {
|
|||||||
#define MiB(x) (KiB(x) * 1024ull)
|
#define MiB(x) (KiB(x) * 1024ull)
|
||||||
#define GiB(x) (MiB(x) * 1024ull)
|
#define GiB(x) (MiB(x) * 1024ull)
|
||||||
#define TiB(x) (GiB(x) * 1024ull)
|
#define TiB(x) (GiB(x) * 1024ull)
|
||||||
#define Lengthof(x) ((int64_t)((sizeof(x) / sizeof((x)[0]))))
|
|
||||||
#define SLICE_LAST INT64_MAX
|
#define SLICE_LAST INT64_MAX
|
||||||
|
#define Lengthof(x) ((int64_t)((sizeof(x) / sizeof((x)[0]))))
|
||||||
|
#define Unused(x) (void)(x)
|
||||||
|
|
||||||
using U8 = uint8_t;
|
using U8 = uint8_t;
|
||||||
using U16 = uint16_t;
|
using U16 = uint16_t;
|
||||||
@@ -122,6 +141,8 @@ using Int = S64;
|
|||||||
using UInt = U64;
|
using UInt = U64;
|
||||||
using Float = double;
|
using Float = double;
|
||||||
|
|
||||||
|
#define IntMax INT64_MAX
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
T Min(T a, T b) {
|
T Min(T a, T b) {
|
||||||
if (a > b) return b;
|
if (a > b) return b;
|
||||||
@@ -237,3 +258,14 @@ inline uint64_t GetRandomU64(RandomSeed *state) {
|
|||||||
#define STRINGIFY(x) STRINGIFY_(x)
|
#define STRINGIFY(x) STRINGIFY_(x)
|
||||||
#define CONCAT_(a, b) a ## b
|
#define CONCAT_(a, b) a ## b
|
||||||
#define CONCAT(a, b) CONCAT_(a, b)
|
#define CONCAT(a, b) CONCAT_(a, b)
|
||||||
|
|
||||||
|
Int SizeToInt(size_t size) {
|
||||||
|
Assert(size <= (size_t)IntMax);
|
||||||
|
return (Int)size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Int Strlen(const char *string) {
|
||||||
|
size_t size = strlen(string);
|
||||||
|
Int result = SizeToInt(size);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -24,10 +24,12 @@ API void (*Error)(const char *, ...);
|
|||||||
struct backtrace_state *backtrace_state = NULL;
|
struct backtrace_state *backtrace_state = NULL;
|
||||||
|
|
||||||
void BacktraceOnError(void *data, const char *msg, int errnum) {
|
void BacktraceOnError(void *data, const char *msg, int errnum) {
|
||||||
|
Unused(data);
|
||||||
Error("libbacktrace error: %s (errnum: %d)\n", msg, errnum);
|
Error("libbacktrace error: %s (errnum: %d)\n", msg, errnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
int BacktraceOnPrint(void *data, uintptr_t pc, const char *filename, int lineno, const char *function) {
|
int BacktraceOnPrint(void *data, uintptr_t pc, const char *filename, int lineno, const char *function) {
|
||||||
|
Unused(data); Unused(pc);
|
||||||
bool printed = false;
|
bool printed = false;
|
||||||
if (filename != NULL) {
|
if (filename != NULL) {
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
@@ -46,6 +48,7 @@ int BacktraceOnPrint(void *data, uintptr_t pc, const char *filename, int lineno,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CrashHandler(int signal, siginfo_t* info, void* context) {
|
void CrashHandler(int signal, siginfo_t* info, void* context) {
|
||||||
|
Unused(signal); Unused(info); Unused(context);
|
||||||
backtrace_full(backtrace_state, 2, BacktraceOnPrint, BacktraceOnError, NULL);
|
backtrace_full(backtrace_state, 2, BacktraceOnPrint, BacktraceOnError, NULL);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@@ -102,7 +105,7 @@ API bool WriteFile(String path, String data) {
|
|||||||
FILE *f = fopen((const char *)null_term.data, "w");
|
FILE *f = fopen((const char *)null_term.data, "w");
|
||||||
if (f) {
|
if (f) {
|
||||||
size_t written = fwrite(data.data, 1, data.len, f);
|
size_t written = fwrite(data.data, 1, data.len, f);
|
||||||
if (written == data.len) {
|
if (SizeToInt(written) == data.len) {
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
fclose(f);
|
fclose(f);
|
||||||
@@ -254,204 +257,6 @@ API FileIter IterateFiles(Allocator alo, String path) {
|
|||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UnixProcess {
|
|
||||||
pid_t pid;
|
|
||||||
int child_stdout_read;
|
|
||||||
int stdin_write;
|
|
||||||
};
|
|
||||||
|
|
||||||
API Array<char *> SplitCommand(Allocator allocator, String command_line) {
|
|
||||||
Array<char *> cmd = {allocator};
|
|
||||||
|
|
||||||
String curr = {};
|
|
||||||
for (int i = 0; i < command_line.len; i += 1) {
|
|
||||||
if (command_line.data[i] == ' ') {
|
|
||||||
if (curr.len > 0) {
|
|
||||||
Add(&cmd, Copy(allocator, curr).data);
|
|
||||||
curr = {};
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (curr.len == 0) {
|
|
||||||
curr.data = command_line.data + i;
|
|
||||||
}
|
|
||||||
curr.len += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curr.len > 0) {
|
|
||||||
Add(&cmd, Copy(allocator, curr).data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array<String> enviroment) {
|
|
||||||
Scratch scratch;
|
|
||||||
const int PIPE_READ = 0;
|
|
||||||
const int PIPE_WRITE = 1;
|
|
||||||
bool error = false;
|
|
||||||
|
|
||||||
working_dir = Copy(scratch, working_dir);
|
|
||||||
chdir(working_dir.data);
|
|
||||||
|
|
||||||
Process process = {};
|
|
||||||
UnixProcess *plat = (UnixProcess *)&process.platform;
|
|
||||||
Array<char *> args = SplitCommand(scratch, command_line);
|
|
||||||
Array<char *> env = {scratch};
|
|
||||||
|
|
||||||
For (enviroment) {
|
|
||||||
Add(&env, Copy(scratch, it).data);
|
|
||||||
}
|
|
||||||
|
|
||||||
int stdout_desc[2] = {};
|
|
||||||
int stdin_desc[2] = {};
|
|
||||||
posix_spawn_file_actions_t actions = {};
|
|
||||||
|
|
||||||
if (posix_spawn_file_actions_init(&actions) != 0) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_init, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
defer {
|
|
||||||
posix_spawn_file_actions_destroy(&actions);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (pipe(stdout_desc) == -1) {
|
|
||||||
Error("Libc function failed: pipe, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
defer {
|
|
||||||
if (error) {
|
|
||||||
close(stdout_desc[PIPE_READ]);
|
|
||||||
close(stdout_desc[PIPE_WRITE]);
|
|
||||||
} else {
|
|
||||||
close(stdout_desc[PIPE_WRITE]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (pipe(stdin_desc) == -1) {
|
|
||||||
Error("Libc function failed: pipe, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
defer {
|
|
||||||
if (error) {
|
|
||||||
close(stdin_desc[PIPE_READ]);
|
|
||||||
close(stdin_desc[PIPE_WRITE]);
|
|
||||||
} else {
|
|
||||||
close(stdin_desc[PIPE_READ]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
error = posix_spawn_file_actions_addclose(&actions, stdout_desc[PIPE_READ]) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDOUT_FILENO) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_adddup2 STDOUT_FILENO, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDERR_FILENO) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_adddup2 STDERR_FILENO, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = posix_spawn_file_actions_addclose(&actions, stdin_desc[PIPE_WRITE]) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = posix_spawn_file_actions_adddup2(&actions, stdin_desc[PIPE_READ], STDIN_FILENO) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: posix_spawn_file_actions_adddup2 STDIN_FILENO, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t process_pid = 0;
|
|
||||||
error = posix_spawnp(&process_pid, args[0], &actions, NULL, args.data, env.data) != 0;
|
|
||||||
if (error) {
|
|
||||||
Error("Libc function failed: failed to create process\n, with error: %s", strerror(errno));
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
plat->child_stdout_read = stdout_desc[PIPE_READ];
|
|
||||||
plat->stdin_write = stdin_desc[PIPE_WRITE];
|
|
||||||
plat->pid = process_pid;
|
|
||||||
|
|
||||||
if (write_stdin.len) {
|
|
||||||
WriteStdin(&process, write_stdin);
|
|
||||||
CloseStdin(&process);
|
|
||||||
}
|
|
||||||
|
|
||||||
process.id = process_pid;
|
|
||||||
process.is_valid = true;
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
API bool IsValid(Process *process) {
|
|
||||||
UnixProcess *plat = (UnixProcess *)&process->platform;
|
|
||||||
if (process->is_valid == false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int status = 0;
|
|
||||||
pollfd p = {};
|
|
||||||
p.fd = plat->child_stdout_read;
|
|
||||||
p.events = POLLRDHUP | POLLERR | POLLHUP | POLLNVAL;
|
|
||||||
int res = poll(&p, 1, 0);
|
|
||||||
if (res > 0) {
|
|
||||||
pid_t result = waitpid(plat->pid, &status, 0);
|
|
||||||
Assert(result != -1);
|
|
||||||
process->exit_code = WEXITSTATUS(status);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
API void KillProcess(Process *process) {
|
|
||||||
Assert(process->is_valid);
|
|
||||||
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
||||||
kill(plat->pid, SIGKILL);
|
|
||||||
process->exit_code = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
API String PollStdout(Allocator allocator, Process *process, bool force_read) {
|
|
||||||
Assert(process->is_valid);
|
|
||||||
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
||||||
|
|
||||||
String result = {};
|
|
||||||
result.data = AllocArray(allocator, char, 16 * 4096);
|
|
||||||
|
|
||||||
pollfd p = {};
|
|
||||||
p.fd = plat->child_stdout_read;
|
|
||||||
p.events = POLLIN;
|
|
||||||
int res = poll(&p, 1, 0);
|
|
||||||
if (res > 0 || force_read) {
|
|
||||||
result.len = read(plat->child_stdout_read, result.data, 4 * 4096);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
API void WriteStdin(Process *process, String string) {
|
|
||||||
if (string.len == 0) return;
|
|
||||||
Assert(process->is_valid);
|
|
||||||
|
|
||||||
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
||||||
ssize_t size = write(plat->stdin_write, string.data, string.len);
|
|
||||||
|
|
||||||
Assert(size == string.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
API void CloseStdin(Process *process) {
|
|
||||||
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
||||||
close(plat->stdin_write);
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif OS_WINDOWS
|
#elif OS_WINDOWS
|
||||||
|
|
||||||
@@ -462,6 +267,7 @@ API void CloseStdin(Process *process) {
|
|||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#endif
|
#endif
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#undef Yield
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
|
|
||||||
@@ -721,210 +527,6 @@ API MakeDirResult MakeDir(String path) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Win32Process {
|
|
||||||
HANDLE handle;
|
|
||||||
HANDLE child_stdout_read;
|
|
||||||
HANDLE child_stdout_write;
|
|
||||||
HANDLE child_stdin_read;
|
|
||||||
HANDLE child_stdin_write;
|
|
||||||
};
|
|
||||||
// static_assert(sizeof(Win32Process) < sizeof(Process::platform));
|
|
||||||
|
|
||||||
static void Win32ReportError(String msg, String cmd) {
|
|
||||||
LPVOID lpMsgBuf;
|
|
||||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL);
|
|
||||||
defer { LocalFree(lpMsgBuf); };
|
|
||||||
char *buff = (char *)lpMsgBuf;
|
|
||||||
size_t buffLen = strlen((const char *)buff);
|
|
||||||
if (buffLen > 0 && buff[buffLen - 1] == '\n') buff[buffLen - 1] = 0;
|
|
||||||
Error("%S: %S! %s", msg, cmd, (char *)lpMsgBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Win32CloseProcess(Process *process) {
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
if (p->handle != INVALID_HANDLE_VALUE) CloseHandle(p->handle);
|
|
||||||
if (p->child_stdout_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_write);
|
|
||||||
if (p->child_stdout_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_read);
|
|
||||||
if (p->child_stdin_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_read);
|
|
||||||
if (p->child_stdin_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_write);
|
|
||||||
process->is_valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Win32ProcessError(Process *process, String msg, String cmd) {
|
|
||||||
Win32ReportError(msg, cmd);
|
|
||||||
Win32CloseProcess(process);
|
|
||||||
}
|
|
||||||
|
|
||||||
API void WriteStdin(Process *process, String string) {
|
|
||||||
if (string.len == 0) return;
|
|
||||||
Assert(process->is_valid);
|
|
||||||
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
Assert(p->child_stdin_write != INVALID_HANDLE_VALUE);
|
|
||||||
|
|
||||||
DWORD written = 0;
|
|
||||||
bool write_error = WriteFile(p->child_stdin_write, string.data, (DWORD)string.len, &written, NULL) == 0;
|
|
||||||
|
|
||||||
Assert(write_error == false);
|
|
||||||
Assert(written == string.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
API void CloseStdin(Process *process) {
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
CloseHandle(p->child_stdin_write);
|
|
||||||
p->child_stdin_write = INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array<String> enviroment) {
|
|
||||||
Process process = {};
|
|
||||||
Win32Process *p = (Win32Process *)process.platform;
|
|
||||||
|
|
||||||
Scratch scratch;
|
|
||||||
command_line = Format(scratch, "/C %S", command_line);
|
|
||||||
|
|
||||||
p->handle = INVALID_HANDLE_VALUE;
|
|
||||||
p->child_stdout_write = INVALID_HANDLE_VALUE;
|
|
||||||
p->child_stdout_read = INVALID_HANDLE_VALUE;
|
|
||||||
p->child_stdin_read = INVALID_HANDLE_VALUE;
|
|
||||||
p->child_stdin_write = INVALID_HANDLE_VALUE;
|
|
||||||
|
|
||||||
SECURITY_ATTRIBUTES security_atrb = {};
|
|
||||||
security_atrb.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
||||||
security_atrb.bInheritHandle = TRUE;
|
|
||||||
|
|
||||||
if (!CreatePipe(&p->child_stdout_read, &p->child_stdout_write, &security_atrb, 0)) {
|
|
||||||
Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line);
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SetHandleInformation(p->child_stdout_read, HANDLE_FLAG_INHERIT, 0)) {
|
|
||||||
Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line);
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CreatePipe(&p->child_stdin_read, &p->child_stdin_write, &security_atrb, 0)) {
|
|
||||||
Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line);
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SetHandleInformation(p->child_stdin_write, HANDLE_FLAG_INHERIT, 0)) {
|
|
||||||
Win32ProcessError(&process, "Failed to create process at create pipe stage", command_line);
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
STARTUPINFOW startup = {};
|
|
||||||
startup.cb = sizeof(STARTUPINFO);
|
|
||||||
startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
|
|
||||||
startup.hStdInput = p->child_stdin_read;
|
|
||||||
startup.hStdOutput = p->child_stdout_write;
|
|
||||||
startup.hStdError = p->child_stdout_write;
|
|
||||||
startup.wShowWindow = SW_HIDE;
|
|
||||||
|
|
||||||
String16 cwd = ToString16(scratch, working_dir);
|
|
||||||
String16 cmd = ToString16(scratch, command_line);
|
|
||||||
|
|
||||||
char *env = NULL;
|
|
||||||
if (enviroment.len) {
|
|
||||||
Int size = GetSize(enviroment) + enviroment.len + 1;
|
|
||||||
env = AllocArray(scratch, char, size);
|
|
||||||
Int i = 0;
|
|
||||||
For(enviroment) {
|
|
||||||
MemoryCopy(env + i, it.data, it.len);
|
|
||||||
i += it.len;
|
|
||||||
|
|
||||||
env[i++] = 0;
|
|
||||||
}
|
|
||||||
env[i++] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD dwCreationFlags = 0;
|
|
||||||
BOOL bInheritHandles = TRUE;
|
|
||||||
|
|
||||||
PROCESS_INFORMATION info = {};
|
|
||||||
if (!CreateProcessW(L"c:\\windows\\system32\\cmd.exe", (wchar_t *)cmd.data, 0, 0, bInheritHandles, dwCreationFlags, env, (wchar_t *)cwd.data, &startup, &info)) {
|
|
||||||
Win32ProcessError(&process, "failed to create process", command_line);
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close handles to the stdin and stdout pipes no longer needed by the child process.
|
|
||||||
// If they are not explicitly closed, there is no way to recognize that the child process has ended.
|
|
||||||
CloseHandle(info.hThread);
|
|
||||||
CloseHandle(p->child_stdin_read);
|
|
||||||
CloseHandle(p->child_stdout_write);
|
|
||||||
p->child_stdin_read = INVALID_HANDLE_VALUE;
|
|
||||||
p->child_stdout_write = INVALID_HANDLE_VALUE;
|
|
||||||
|
|
||||||
p->handle = info.hProcess;
|
|
||||||
process.is_valid = true;
|
|
||||||
process.id = (int64_t)p->handle;
|
|
||||||
|
|
||||||
if (write_stdin.len) {
|
|
||||||
WriteStdin(&process, write_stdin);
|
|
||||||
CloseStdin(&process);
|
|
||||||
}
|
|
||||||
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
|
|
||||||
API bool IsValid(Process *process) {
|
|
||||||
if (process->is_valid == false) return false;
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
|
|
||||||
if (WaitForSingleObject(p->handle, 0) != WAIT_OBJECT_0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD exit_code;
|
|
||||||
bool get_exit_code_failed = GetExitCodeProcess(p->handle, &exit_code) == 0;
|
|
||||||
if (get_exit_code_failed) {
|
|
||||||
exit_code = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
process->exit_code = (int)exit_code;
|
|
||||||
Win32CloseProcess(process);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
API void KillProcess(Process *process) {
|
|
||||||
Assert(process->is_valid);
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
|
|
||||||
bool terminate_process_error = TerminateProcess(p->handle, -1) == 0;
|
|
||||||
if (terminate_process_error) {
|
|
||||||
Assert(0);
|
|
||||||
}
|
|
||||||
Win32CloseProcess(process);
|
|
||||||
process->exit_code = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
API String PollStdout(Allocator allocator, Process *process, bool force_read) {
|
|
||||||
Assert(process->is_valid);
|
|
||||||
Win32Process *p = (Win32Process *)process->platform;
|
|
||||||
|
|
||||||
DWORD bytes_avail = 0;
|
|
||||||
bool peek_error = PeekNamedPipe(p->child_stdout_read, NULL, 0, NULL, &bytes_avail, NULL) == 0;
|
|
||||||
if (peek_error) {
|
|
||||||
return {};
|
|
||||||
} else if (bytes_avail == 0) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t buffer_size = ClampTop(bytes_avail, (DWORD)(4096 * 16));
|
|
||||||
char *buffer = AllocArray(allocator, char, buffer_size);
|
|
||||||
|
|
||||||
DWORD bytes_read = 0;
|
|
||||||
bool read_error = ReadFile(p->child_stdout_read, buffer, (DWORD)buffer_size, &bytes_read, 0) == 0;
|
|
||||||
if (read_error) {
|
|
||||||
Win32ReportError("Failed to read the stdout of child process", "ReadFile");
|
|
||||||
Dealloc(allocator, buffer);
|
|
||||||
Win32CloseProcess(process);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
String result = {buffer, bytes_read};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif OS_WASM
|
#elif OS_WASM
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -1118,56 +720,30 @@ API FileIter IterateFiles(Allocator alo, String path) {
|
|||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
API Array<char *> SplitCommand(Allocator allocator, String command_line) {
|
|
||||||
Array<char *> cmd = {allocator};
|
|
||||||
|
|
||||||
String curr = {};
|
|
||||||
for (int i = 0; i < command_line.len; i += 1) {
|
|
||||||
if (command_line.data[i] == ' ') {
|
|
||||||
if (curr.len > 0) {
|
|
||||||
Add(&cmd, Copy(allocator, curr).data);
|
|
||||||
curr = {};
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (curr.len == 0) {
|
|
||||||
curr.data = command_line.data + i;
|
|
||||||
}
|
|
||||||
curr.len += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curr.len > 0) {
|
|
||||||
Add(&cmd, Copy(allocator, curr).data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
API Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array<String> enviroment) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
API bool IsValid(Process *process) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
API void KillProcess(Process *process) {
|
|
||||||
}
|
|
||||||
|
|
||||||
API String PollStdout(Allocator allocator, Process *process, bool force_read) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
API void WriteStdin(Process *process, String string) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
API void CloseStdin(Process *process) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
API double GetTimeSeconds() {
|
API double GetTimeSeconds() {
|
||||||
return GetTimeMicros() / 1000000.0;
|
return GetTimeMicros() / 1000000.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
API String WriteTempFile(String data) {
|
||||||
|
Scratch scratch;
|
||||||
|
|
||||||
|
#if OS_WINDOWS
|
||||||
|
int buffer_len = MAX_PATH+1;
|
||||||
|
char16_t *buffer = AllocArray(scratch, char16_t, buffer_len);
|
||||||
|
Assert(sizeof(char16_t) == sizeof(wchar_t));
|
||||||
|
DWORD result = GetTempPath2W(buffer_len, (LPWSTR)buffer);
|
||||||
|
Assert(result != 0);
|
||||||
|
String16 temp16 = {buffer, result};
|
||||||
|
NormalizePathInPlace(temp16);
|
||||||
|
String temp_directory = ToString(scratch, temp16);
|
||||||
|
#else
|
||||||
|
String temp_directory = "/tmp";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
String temp_filename = Format(scratch, "%S/temp%llu", temp_directory, GetTimeNanos());
|
||||||
|
bool done = WriteFile(temp_filename, data);
|
||||||
|
Assert(done);
|
||||||
|
return temp_filename;
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,22 +33,9 @@ String GetWorkingDir(Allocator arena);
|
|||||||
bool IsAbsolute(String path);
|
bool IsAbsolute(String path);
|
||||||
int64_t GetFileModTime(String file);
|
int64_t GetFileModTime(String file);
|
||||||
|
|
||||||
struct Process {
|
|
||||||
bool is_valid;
|
|
||||||
int exit_code;
|
|
||||||
char platform[6 * 8];
|
|
||||||
int64_t id;
|
|
||||||
|
|
||||||
int64_t view_id; // text editor view
|
|
||||||
bool scroll_to_end;
|
|
||||||
};
|
|
||||||
|
|
||||||
Process SpawnProcess(String command_line, String working_dir, String write_stdin = {}, Array<String> enviroment = {});
|
|
||||||
bool IsValid(Process *process);
|
|
||||||
void KillProcess(Process *process);
|
|
||||||
String PollStdout(Allocator allocator, Process *process, bool force_read);
|
|
||||||
void WriteStdin(Process *process, String string);
|
|
||||||
void CloseStdin(Process *process);
|
|
||||||
double GetTimeMicros(void);
|
double GetTimeMicros(void);
|
||||||
|
|
||||||
enum MakeDirResult {
|
enum MakeDirResult {
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ API String Copy(Allocator allocator, String string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
API String Copy(Allocator allocator, char *string) {
|
API String Copy(Allocator allocator, char *string) {
|
||||||
return Copy(allocator, {string, (int64_t)strlen(string)});
|
return Copy(allocator, {string, Strlen(string)});
|
||||||
}
|
}
|
||||||
|
|
||||||
API void NormalizePathInPlace(String s) {
|
API void NormalizePathInPlace(String s) {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ struct String {
|
|||||||
int64_t len;
|
int64_t len;
|
||||||
|
|
||||||
String() = default;
|
String() = default;
|
||||||
String(char *s) : data(s), len(strlen(s)) {}
|
String(char *s) : data(s), len(Strlen(s)) {}
|
||||||
String(char *s, int64_t l) : data((char *)s), len(l) {}
|
String(char *s, int64_t l) : data((char *)s), len(l) {}
|
||||||
String(const char *s) : data((char *)s), len(strlen((char *)s)) {}
|
String(const char *s) : data((char *)s), len(Strlen((char *)s)) {}
|
||||||
String(const char *s, int64_t l) : data((char *)s), len(l) {}
|
String(const char *s, int64_t l) : data((char *)s), len(l) {}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ API bool IsDigit(char16_t a) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
API bool IsHexDigit(char16_t a) {
|
API bool IsHexDigit(char16_t a) {
|
||||||
bool result = a >= u'0' && a <= u'9' || a == 'a' || a == 'b' || a == 'c' || a == 'd' || a == 'e' || a == 'f';
|
bool result = (a >= u'0' && a <= u'9') || a == 'a' || a == 'b' || a == 'c' || a == 'd' || a == 'e' || a == 'f';
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,14 @@ Vec2I GetMid(Rect2I r) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rect2 Round(Rect2 r) {
|
||||||
|
r.min.x = roundf(r.min.x);
|
||||||
|
r.max.x = roundf(r.max.x);
|
||||||
|
r.min.y = roundf(r.min.y);
|
||||||
|
r.max.y = roundf(r.max.y);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
Rect2I CutLeft(Rect2I *r, Int value) {
|
Rect2I CutLeft(Rect2I *r, Int value) {
|
||||||
Int minx = r->min.x;
|
Int minx = r->min.x;
|
||||||
r->min.x = Min(r->min.x + value, r->max.x);
|
r->min.x = Min(r->min.x + value, r->max.x);
|
||||||
@@ -143,3 +151,7 @@ bool AreOverlapping(Vec2I point, Rect2I rec) {
|
|||||||
bool result = (point.x >= rec.min.x) && (point.x < rec.max.x) && (point.y >= rec.min.y) && (point.y < rec.max.y);
|
bool result = (point.x >= rec.min.x) && (point.x < rec.max.x) && (point.y >= rec.min.y) && (point.y < rec.max.y);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Int Absolute(Int value) {
|
||||||
|
return llabs(value);
|
||||||
|
}
|
||||||
5
src/external/minicoro.h
vendored
5
src/external/minicoro.h
vendored
@@ -429,8 +429,8 @@ MCO_API const char *mco_result_description(mco_result res); /* Get the descripti
|
|||||||
#else
|
#else
|
||||||
#ifdef thread_local
|
#ifdef thread_local
|
||||||
#define MCO_THREAD_LOCAL thread_local
|
#define MCO_THREAD_LOCAL thread_local
|
||||||
#elif __STDC_VERSION__ >= 201112 && !defined(__STDC_NO_THREADS__)
|
// #elif __STDC_VERSION__ >= 201112 && !defined(__STDC_NO_THREADS__)
|
||||||
#define MCO_THREAD_LOCAL _Thread_local
|
// #define MCO_THREAD_LOCAL _Thread_local
|
||||||
#elif defined(_WIN32) && (defined(_MSC_VER) || defined(__ICL) || defined(__DMC__) || defined(__BORLANDC__))
|
#elif defined(_WIN32) && (defined(_MSC_VER) || defined(__ICL) || defined(__DMC__) || defined(__BORLANDC__))
|
||||||
#define MCO_THREAD_LOCAL __declspec(thread)
|
#define MCO_THREAD_LOCAL __declspec(thread)
|
||||||
#elif defined(__GNUC__) || defined(__SUNPRO_C) || defined(__xlC__)
|
#elif defined(__GNUC__) || defined(__SUNPRO_C) || defined(__xlC__)
|
||||||
@@ -476,6 +476,7 @@ MCO_API const char *mco_result_description(mco_result res); /* Get the descripti
|
|||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#endif
|
#endif
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#undef Yield
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef MCO_NO_DEFAULT_ALLOCATOR
|
#ifndef MCO_NO_DEFAULT_ALLOCATOR
|
||||||
|
|||||||
@@ -73,9 +73,10 @@ static const char *glsl_fshader_es3 = R"==(#version 300 es
|
|||||||
}
|
}
|
||||||
)==";
|
)==";
|
||||||
|
|
||||||
void ReportWarningf(const char *fmt, ...);
|
void ReportErrorf(const char *fmt, ...);
|
||||||
void GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *user) {
|
void GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *user) {
|
||||||
ReportWarningf("OpenGL message: %s", message);
|
Unused(source); Unused(type); Unused(id); Unused(length); Unused(user);
|
||||||
|
ReportErrorf("OpenGL message: %s", message);
|
||||||
if (severity == GL_DEBUG_SEVERITY_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM) {
|
if (severity == GL_DEBUG_SEVERITY_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM) {
|
||||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "OpenGL error", message, NULL);
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "OpenGL error", message, NULL);
|
||||||
}
|
}
|
||||||
@@ -288,7 +289,7 @@ void PushVertex2D(Allocator allocator, VertexList2D *list, Vertex2D *vertices, i
|
|||||||
for (int i = 0; i < count; i += 1) result[i] = vertices[i];
|
for (int i = 0; i < count; i += 1) result[i] = vertices[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushQuad2D(Allocator arena, VertexList2D *list, Rect2 rect, Rect2 tex, Color color, float rotation = 0.f, Vec2 rotation_point = {}) {
|
void PushQuad2D(Allocator arena, VertexList2D *list, Rect2 rect, Rect2 tex, Color color) {
|
||||||
Vertex2D *v = AllocVertex2D(arena, list, 6);
|
Vertex2D *v = AllocVertex2D(arena, list, 6);
|
||||||
v[0] = { {rect.min.x, rect.max.y}, { tex.min.x, tex.max.y}, color };
|
v[0] = { {rect.min.x, rect.max.y}, { tex.min.x, tex.max.y}, color };
|
||||||
v[1] = { {rect.max.x, rect.max.y}, { tex.max.x, tex.max.y}, color };
|
v[1] = { {rect.max.x, rect.max.y}, { tex.max.x, tex.max.y}, color };
|
||||||
@@ -296,15 +297,6 @@ void PushQuad2D(Allocator arena, VertexList2D *list, Rect2 rect, Rect2 tex, Colo
|
|||||||
v[3] = { {rect.min.x, rect.min.y}, { tex.min.x, tex.min.y}, color };
|
v[3] = { {rect.min.x, rect.min.y}, { tex.min.x, tex.min.y}, color };
|
||||||
v[4] = { {rect.max.x, rect.max.y}, { tex.max.x, tex.max.y}, color };
|
v[4] = { {rect.max.x, rect.max.y}, { tex.max.x, tex.max.y}, color };
|
||||||
v[5] = { {rect.max.x, rect.min.y}, { tex.max.x, tex.min.y}, color };
|
v[5] = { {rect.max.x, rect.min.y}, { tex.max.x, tex.min.y}, color };
|
||||||
if (rotation != 0.f) {
|
|
||||||
float s = sinf(rotation);
|
|
||||||
float c = cosf(rotation);
|
|
||||||
for (int i = 0; i < 6; i += 1) {
|
|
||||||
v[i].pos -= rotation_point;
|
|
||||||
v[i].pos = {v[i].pos.x * c + v[i].pos.y * (-s), v[i].pos.x * s + v[i].pos.y * c};
|
|
||||||
v[i].pos += rotation_point;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawRect(Rect2 rect, Color color) {
|
void DrawRect(Rect2 rect, Color color) {
|
||||||
@@ -336,7 +328,7 @@ Vec2 DrawString(Font *font, String16 string, Vec2 pos, Color color, bool draw =
|
|||||||
Glyph *g = GetGlyph(font, it);
|
Glyph *g = GetGlyph(font, it);
|
||||||
Rect2 rect = Rect2FromSize(pos + g->offset, g->size);
|
Rect2 rect = Rect2FromSize(pos + g->offset, g->size);
|
||||||
if (draw && it != '\n' && it != ' ' && it != '\t') {
|
if (draw && it != '\n' && it != ' ' && it != '\t') {
|
||||||
PushQuad2D(RenderArena, &Vertices, rect, g->atlas_bounding_box, color);
|
PushQuad2D(RenderArena, &Vertices, Round(rect), g->atlas_bounding_box, color);
|
||||||
}
|
}
|
||||||
pos.x += g->xadvance;
|
pos.x += g->xadvance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,12 @@ void AddText(String string) {
|
|||||||
|
|
||||||
void Wait(mco_coro *co) {
|
void Wait(mco_coro *co) {
|
||||||
Add(&EventPlayback, {EVENT_KIND_INVALID});
|
Add(&EventPlayback, {EVENT_KIND_INVALID});
|
||||||
for (Event *event = CoYield(co); event->kind != EVENT_KIND_INVALID; event = CoYield(co)) {
|
for (Event *event = Yield(co); event->kind != EVENT_KIND_INVALID; event = Yield(co)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayTestOpen(mco_coro *co) {
|
void PlayTestOpen(mco_coro *co) {
|
||||||
|
Unused(co);
|
||||||
// Open file, move a little, then open again and verify the caret didn't move
|
// Open file, move a little, then open again and verify the caret didn't move
|
||||||
// String basic_env_cpp = Format(SysAllocator, "%S/test_env", TestDir);
|
// String basic_env_cpp = Format(SysAllocator, "%S/test_env", TestDir);
|
||||||
|
|
||||||
@@ -153,8 +154,8 @@ void InitTests() {
|
|||||||
WaitForEvents = false;
|
WaitForEvents = false;
|
||||||
TestDir = Format(TestArena, "%S/test_env", GetExeDir(TestArena));
|
TestDir = Format(TestArena, "%S/test_env", GetExeDir(TestArena));
|
||||||
|
|
||||||
CoRemove("Test");
|
RemoveCoroutine("Test");
|
||||||
CoData *data = CoAdd(Test);
|
CCtx *data = AddCoroutine(Test);
|
||||||
data->dont_wait_until_resolved = true;
|
data->dont_wait_until_resolved = true;
|
||||||
CoResume(data);
|
ResumeCoroutine(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
#define BUFFER_DEBUG 0
|
#define BUFFER_DEBUG SLOW_BUILD
|
||||||
|
|
||||||
|
API bool AreEqual(XY a, XY b) {
|
||||||
|
bool result = a.x == b.x && a.y == b.y;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
API Range MakeRange(Int a, Int b) {
|
API Range MakeRange(Int a, Int b) {
|
||||||
Range result = {Min(a, b), Max(a, b)};
|
Range result = {Min(a, b), Max(a, b)};
|
||||||
@@ -59,11 +64,6 @@ API Range GetBufferEndAsRange(Buffer *buffer) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
API Range GetBufferBeginAsRange(Buffer *buffer) {
|
|
||||||
Range result = {0, 0};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
API Range GetRange(Buffer *buffer) {
|
API Range GetRange(Buffer *buffer) {
|
||||||
Range result = {0, buffer->len};
|
Range result = {0, buffer->len};
|
||||||
return result;
|
return result;
|
||||||
@@ -160,7 +160,7 @@ API bool AreOverlapping(Caret a, Caret b) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
API Range GetLineRange(Buffer *buffer, Int line, Int *end_of_buffer) {
|
API Range GetLineRange(Buffer *buffer, Int line, Int *end_of_buffer = NULL) {
|
||||||
Range result = {buffer->line_starts[line], buffer->len};
|
Range result = {buffer->line_starts[line], buffer->len};
|
||||||
if (line + 1 < buffer->line_starts.len) {
|
if (line + 1 < buffer->line_starts.len) {
|
||||||
result.max = buffer->line_starts[line + 1];
|
result.max = buffer->line_starts[line + 1];
|
||||||
@@ -177,14 +177,14 @@ API Range GetLineRangeWithoutNL(Buffer *buffer, Int line) {
|
|||||||
return line_range;
|
return line_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
API String16 GetString(Buffer *buffer, Range range) {
|
API String16 GetString(Buffer *buffer, Range range = {0, INT64_MAX}) {
|
||||||
range.min = Clamp(range.min, (Int)0, buffer->len);
|
range.min = Clamp(range.min, (Int)0, buffer->len);
|
||||||
range.max = Clamp(range.max, (Int)0, buffer->len);
|
range.max = Clamp(range.max, (Int)0, buffer->len);
|
||||||
String16 result = {(char16_t *)buffer->data + range.min, GetSize(range)};
|
String16 result = {(char16_t *)buffer->data + range.min, GetSize(range)};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
API String AllocCharString(Allocator allocator, Buffer *buffer, Range range) {
|
API String AllocCharString(Allocator allocator, Buffer *buffer, Range range = {0, INT64_MAX}) {
|
||||||
String16 string16 = GetString(buffer, range);
|
String16 string16 = GetString(buffer, range);
|
||||||
String result = ToString(allocator, string16);
|
String result = ToString(allocator, string16);
|
||||||
return result;
|
return result;
|
||||||
@@ -211,7 +211,7 @@ API Int LastLine(Buffer *buffer) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
API String16 GetLineString(Buffer *buffer, Int line, Int *end_of_buffer) {
|
API String16 GetLineString(Buffer *buffer, Int line, Int *end_of_buffer = NULL) {
|
||||||
Range range = GetLineRange(buffer, line, end_of_buffer);
|
Range range = GetLineRange(buffer, line, end_of_buffer);
|
||||||
String16 string = GetString(buffer, range);
|
String16 string = GetString(buffer, range);
|
||||||
return string;
|
return string;
|
||||||
@@ -226,11 +226,9 @@ API String16 GetLineStringWithoutNL(Buffer *buffer, Int line) {
|
|||||||
API Int PosToLine(Buffer *buffer, Int pos) {
|
API Int PosToLine(Buffer *buffer, Int pos) {
|
||||||
Add(&buffer->line_starts, buffer->len + 1);
|
Add(&buffer->line_starts, buffer->len + 1);
|
||||||
|
|
||||||
// binary search
|
|
||||||
Int low = 0;
|
|
||||||
|
|
||||||
// -2 here because we use 2 indices and combine them into one line range so we
|
// -2 here because we use 2 indices and combine them into one line range so we
|
||||||
// don't want to access last item since that would index past array.
|
// don't want to access last item since that would index past array.
|
||||||
|
Int low = 0;
|
||||||
Int high = buffer->line_starts.len - 2;
|
Int high = buffer->line_starts.len - 2;
|
||||||
Int result = 0;
|
Int result = 0;
|
||||||
|
|
||||||
@@ -307,7 +305,7 @@ API Int GetWordEnd(Buffer *buffer, Int pos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
API bool IsLoadWord(char16_t w) {
|
API bool IsLoadWord(char16_t w) {
|
||||||
bool result = w == u'-' || w == u'/' || w == u'\\' || w == u':' || w == u'$' || w == u'_' || w == u'.' || w == u'!' || w == u'@';
|
bool result = w == u'-' || w == u'/' || w == u'\\' || w == u':' || w == u'$' || w == u'_' || w == u'.' || w == u'!' || w == u'@' || w == '{' || w == '}';
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = !(IsSymbol(w) || IsWhitespace(w));
|
result = !(IsSymbol(w) || IsWhitespace(w));
|
||||||
}
|
}
|
||||||
@@ -417,8 +415,7 @@ API Int OnCharClassBoundary_GetNextWordEnd(Buffer *buffer, Int pos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
API Int OnCharClassBoundary_GetPrevWordStart(Buffer *buffer, Int pos) {
|
API Int OnCharClassBoundary_GetPrevWordStart(Buffer *buffer, Int pos) {
|
||||||
pos = Clamp(pos, (Int)0, buffer->len);
|
Int i = Clamp(pos - 1, (Int)0, buffer->len);
|
||||||
Int i = pos - 1;
|
|
||||||
|
|
||||||
if (i >= 0 && GetCharClass(buffer->str[i]) == CharClass_Whitespace) {
|
if (i >= 0 && GetCharClass(buffer->str[i]) == CharClass_Whitespace) {
|
||||||
i -= 1;
|
i -= 1;
|
||||||
@@ -453,7 +450,7 @@ API Int GetFullLineStart(Buffer *buffer, Int pos) {
|
|||||||
return range.min;
|
return range.min;
|
||||||
}
|
}
|
||||||
|
|
||||||
API Int GetFullLineEnd(Buffer *buffer, Int pos, Int *eof) {
|
API Int GetFullLineEnd(Buffer *buffer, Int pos, Int *eof = NULL) {
|
||||||
pos = Clamp(pos, (Int)0, buffer->len);
|
pos = Clamp(pos, (Int)0, buffer->len);
|
||||||
Int line = PosToLine(buffer, pos);
|
Int line = PosToLine(buffer, pos);
|
||||||
Range range = GetLineRange(buffer, line, eof);
|
Range range = GetLineRange(buffer, line, eof);
|
||||||
@@ -464,10 +461,6 @@ API Int GetBufferEnd(Buffer *buffer) {
|
|||||||
return buffer->len;
|
return buffer->len;
|
||||||
}
|
}
|
||||||
|
|
||||||
API Int GetBufferStart(Buffer *buffer) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
API Int GetNextEmptyLineStart(Buffer *buffer, Int pos) {
|
API Int GetNextEmptyLineStart(Buffer *buffer, Int pos) {
|
||||||
Int result = pos;
|
Int result = pos;
|
||||||
Int next_line = PosToLine(buffer, pos) + 1;
|
Int next_line = PosToLine(buffer, pos) + 1;
|
||||||
@@ -666,6 +659,9 @@ void UpdateLines(Buffer *buffer, Range range, String16 string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RawValidateLineStarts(Buffer *buffer) {
|
void RawValidateLineStarts(Buffer *buffer) {
|
||||||
|
if (buffer->no_line_starts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Int line = 0;
|
Int line = 0;
|
||||||
for (Int i = 0; i < buffer->len; i += 1) {
|
for (Int i = 0; i < buffer->len; i += 1) {
|
||||||
Int l = PosToLine(buffer, i);
|
Int l = PosToLine(buffer, i);
|
||||||
@@ -674,6 +670,8 @@ void RawValidateLineStarts(Buffer *buffer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These don't handle history, just raw operations on buffer memory
|
||||||
|
// TODO: MAYBE THEY SHOULD!
|
||||||
API void RawReplaceText(Buffer *buffer, Range range, String16 string) {
|
API void RawReplaceText(Buffer *buffer, Range range, String16 string) {
|
||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
Assert(range.max >= range.min);
|
Assert(range.max >= range.min);
|
||||||
@@ -738,7 +736,6 @@ inline bool MergeSortCompare(Edit *EntryA, Edit *EntryB) {
|
|||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
void MergeSort(int64_t Count, T *First, T *Temp) {
|
void MergeSort(int64_t Count, T *First, T *Temp) {
|
||||||
ProfileFunction();
|
|
||||||
// SortKey = range.min
|
// SortKey = range.min
|
||||||
if (Count == 1) {
|
if (Count == 1) {
|
||||||
// NOTE(casey): No work to do.
|
// NOTE(casey): No work to do.
|
||||||
@@ -791,7 +788,9 @@ void MergeSort(int64_t Count, T *First, T *Temp) {
|
|||||||
API void ApplyEditsMultiCursor(Buffer *buffer, Array<Edit> edits) {
|
API void ApplyEditsMultiCursor(Buffer *buffer, Array<Edit> edits) {
|
||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
#if BUFFER_DEBUG
|
#if BUFFER_DEBUG
|
||||||
Assert(buffer->line_starts.len);
|
if (buffer->no_line_starts == false) {
|
||||||
|
Assert(buffer->line_starts.len);
|
||||||
|
}
|
||||||
Assert(edits.len);
|
Assert(edits.len);
|
||||||
For(edits) {
|
For(edits) {
|
||||||
Assert(it.range.min >= 0);
|
Assert(it.range.min >= 0);
|
||||||
@@ -816,7 +815,9 @@ API void ApplyEditsMultiCursor(Buffer *buffer, Array<Edit> edits) {
|
|||||||
// We need to sort from lowest to highest based on range.min
|
// We need to sort from lowest to highest based on range.min
|
||||||
Scratch scratch(buffer->line_starts.allocator);
|
Scratch scratch(buffer->line_starts.allocator);
|
||||||
Array<Edit> edits_copy = TightCopy(scratch, edits);
|
Array<Edit> edits_copy = TightCopy(scratch, edits);
|
||||||
if (edits.len > 1) MergeSort(edits.len, edits_copy.data, edits.data);
|
if (edits.len > 1) {
|
||||||
|
MergeSort(edits.len, edits_copy.data, edits.data);
|
||||||
|
}
|
||||||
edits = edits_copy;
|
edits = edits_copy;
|
||||||
|
|
||||||
#if BUFFER_DEBUG
|
#if BUFFER_DEBUG
|
||||||
@@ -891,53 +892,61 @@ void SaveHistoryBeforeApplyEdits(Buffer *buffer, Array<HistoryEntry> *stack, Arr
|
|||||||
API void RedoEdit(Buffer *buffer, Array<Caret> *carets) {
|
API void RedoEdit(Buffer *buffer, Array<Caret> *carets) {
|
||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
if (buffer->no_history) return;
|
if (buffer->no_history) return;
|
||||||
if (buffer->redo_stack.len <= 0) return;
|
|
||||||
|
|
||||||
HistoryEntry entry = Pop(&buffer->redo_stack);
|
for (;buffer->redo_stack.len > 0;) {
|
||||||
|
HistoryEntry entry = Pop(&buffer->redo_stack);
|
||||||
|
HistoryEntry *e = SaveHistoryBeforeMergeCursor(buffer, &buffer->undo_stack, *carets);
|
||||||
|
e->time = entry.time;
|
||||||
|
SaveHistoryBeforeApplyEdits(buffer, &buffer->undo_stack, entry.edits);
|
||||||
|
ApplyEditsMultiCursor(buffer, entry.edits);
|
||||||
|
|
||||||
HistoryEntry *e = SaveHistoryBeforeMergeCursor(buffer, &buffer->undo_stack, *carets);
|
Dealloc(carets);
|
||||||
e->time = entry.time;
|
*carets = entry.carets;
|
||||||
SaveHistoryBeforeApplyEdits(buffer, &buffer->undo_stack, entry.edits);
|
|
||||||
ApplyEditsMultiCursor(buffer, entry.edits);
|
|
||||||
|
|
||||||
Dealloc(carets);
|
Allocator sys_allocator = GetSystemAllocator();
|
||||||
*carets = entry.carets;
|
For(entry.edits) Dealloc(sys_allocator, it.string.data);
|
||||||
|
Dealloc(&entry.edits);
|
||||||
|
|
||||||
Allocator sys_allocator = GetSystemAllocator();
|
if (buffer->redo_stack.len > 0) {
|
||||||
For(entry.edits) Dealloc(sys_allocator, it.string.data);
|
HistoryEntry *next = GetLast(buffer->redo_stack);
|
||||||
Dealloc(&entry.edits);
|
if ((next->time - entry.time) <= UndoMergeTime) {
|
||||||
|
continue;
|
||||||
if (buffer->redo_stack.len > 0) {
|
}
|
||||||
HistoryEntry *next = GetLast(buffer->redo_stack);
|
|
||||||
if ((next->time - entry.time) <= UndoMergeTime) {
|
|
||||||
RedoEdit(buffer, carets);
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
API void UndoEdit(Buffer *buffer, Array<Caret> *carets) {
|
API void UndoEdit(Buffer *buffer, Array<Caret> *carets) {
|
||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
if (buffer->no_history) return;
|
if (buffer->no_history) return;
|
||||||
if (buffer->undo_stack.len <= 0) return;
|
|
||||||
|
|
||||||
HistoryEntry entry = Pop(&buffer->undo_stack);
|
static bool warning_reported;
|
||||||
HistoryEntry *e = SaveHistoryBeforeMergeCursor(buffer, &buffer->redo_stack, *carets);
|
for (int i = 0;buffer->undo_stack.len > 0; i += 1) {
|
||||||
e->time = entry.time;
|
HistoryEntry entry = Pop(&buffer->undo_stack);
|
||||||
SaveHistoryBeforeApplyEdits(buffer, &buffer->redo_stack, entry.edits);
|
HistoryEntry *e = SaveHistoryBeforeMergeCursor(buffer, &buffer->redo_stack, *carets);
|
||||||
ApplyEditsMultiCursor(buffer, entry.edits);
|
e->time = entry.time;
|
||||||
|
SaveHistoryBeforeApplyEdits(buffer, &buffer->redo_stack, entry.edits);
|
||||||
|
ApplyEditsMultiCursor(buffer, entry.edits);
|
||||||
|
|
||||||
Dealloc(carets);
|
Dealloc(carets);
|
||||||
*carets = entry.carets;
|
*carets = entry.carets;
|
||||||
|
|
||||||
Allocator sys_allocator = GetSystemAllocator();
|
Allocator sys_allocator = GetSystemAllocator();
|
||||||
For(entry.edits) Dealloc(sys_allocator, it.string.data);
|
For(entry.edits) Dealloc(sys_allocator, it.string.data);
|
||||||
Dealloc(&entry.edits);
|
Dealloc(&entry.edits);
|
||||||
|
|
||||||
if (buffer->undo_stack.len > 0) {
|
if (i > 1000 && !warning_reported) {
|
||||||
HistoryEntry *next = GetLast(buffer->undo_stack);
|
ReportConsolef("WARNING: Undoing more then 1000 edits at once, optimizations is needed I think, too much memory usage?");
|
||||||
if ((entry.time - next->time) <= UndoMergeTime) {
|
warning_reported = true;
|
||||||
UndoEdit(buffer, carets);
|
|
||||||
}
|
}
|
||||||
|
if (buffer->undo_stack.len > 0) {
|
||||||
|
HistoryEntry *next = GetLast(buffer->undo_stack);
|
||||||
|
if ((entry.time - next->time) <= UndoMergeTime) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -986,6 +995,9 @@ API Array<Edit> BeginEdit(Allocator allocator, Buffer *buffer, Array<Caret> &car
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We can invoke SaveCaretHistoryBeforeBeginEdit(which is basically BeginEdit with
|
||||||
|
// different name before caret altering commands to save caret history
|
||||||
|
// and then call some editing command to edit which is not going to save carets
|
||||||
API void SaveCaretHistoryBeforeBeginEdit(Buffer *buffer, Array<Caret> &carets) {
|
API void SaveCaretHistoryBeforeBeginEdit(Buffer *buffer, Array<Caret> &carets) {
|
||||||
BeginEdit({}, buffer, carets);
|
BeginEdit({}, buffer, carets);
|
||||||
}
|
}
|
||||||
@@ -996,6 +1008,9 @@ API void AssertRanges(Array<Caret> carets) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adjusts caret copies after edit to make them not move after for example
|
||||||
|
// a bar modification. Sometimes works, sometimes doesn't, depends, not an all solving tool.
|
||||||
|
// For example in case of ReopenBuffer, when we select and replace entire buffer, it didn't quite work.
|
||||||
API void AdjustCarets(Array<Edit> edits, Array<Caret> *carets) {
|
API void AdjustCarets(Array<Edit> edits, Array<Caret> *carets) {
|
||||||
Scratch scratch;
|
Scratch scratch;
|
||||||
Array<Caret> new_carets = TightCopy(scratch, *carets);
|
Array<Caret> new_carets = TightCopy(scratch, *carets);
|
||||||
@@ -1018,7 +1033,9 @@ API void AdjustCarets(Array<Edit> edits, Array<Caret> *carets) {
|
|||||||
for (Int i = 0; i < carets->len; i += 1) carets->data[i] = new_carets[i];
|
for (Int i = 0; i < carets->len; i += 1) carets->data[i] = new_carets[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool kill_selection) {
|
constexpr bool EndEdit_KillSelection = true;
|
||||||
|
constexpr bool EndEdit_SkipFixingCaretsIWantToDoThatMyself = true;
|
||||||
|
API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool kill_selection, bool skip_fixing_carets_user_will_do_that_himself = false) {
|
||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1037,6 +1054,10 @@ API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool
|
|||||||
Assert(buffer->edit_phase == 2);
|
Assert(buffer->edit_phase == 2);
|
||||||
buffer->edit_phase -= 2;
|
buffer->edit_phase -= 2;
|
||||||
|
|
||||||
|
if (edits->len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if BUFFER_DEBUG
|
#if BUFFER_DEBUG
|
||||||
if (buffer->no_history == false) {
|
if (buffer->no_history == false) {
|
||||||
HistoryEntry *entry = GetLast(buffer->undo_stack);
|
HistoryEntry *entry = GetLast(buffer->undo_stack);
|
||||||
@@ -1048,40 +1069,42 @@ API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Adjust carets
|
if (skip_fixing_carets_user_will_do_that_himself == false) {
|
||||||
// this one also moves the carets forward if they are aligned with edit
|
// Adjust carets
|
||||||
//
|
// this one also moves the carets forward if they are aligned with edit
|
||||||
Scratch scratch;
|
//
|
||||||
Array<Caret> new_carets = TightCopy(scratch, *carets);
|
Scratch scratch;
|
||||||
ForItem(edit, *edits) {
|
Array<Caret> new_carets = TightCopy(scratch, *carets);
|
||||||
Int remove_size = GetSize(edit.range);
|
ForItem(edit, *edits) {
|
||||||
Int insert_size = edit.string.len;
|
Int remove_size = GetSize(edit.range);
|
||||||
Int offset = insert_size - remove_size;
|
Int insert_size = edit.string.len;
|
||||||
|
Int offset = insert_size - remove_size;
|
||||||
|
|
||||||
|
for (Int i = 0; i < carets->len; i += 1) {
|
||||||
|
Caret &old_cursor = carets->data[i];
|
||||||
|
Caret &new_cursor = new_carets.data[i];
|
||||||
|
|
||||||
|
if (old_cursor.range.min == edit.range.min) {
|
||||||
|
new_cursor.range.min += insert_size;
|
||||||
|
} else if (old_cursor.range.min > edit.range.min) {
|
||||||
|
new_cursor.range.min += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_cursor.range.max == edit.range.max) {
|
||||||
|
new_cursor.range.max += insert_size;
|
||||||
|
} else if (old_cursor.range.max > edit.range.max) {
|
||||||
|
new_cursor.range.max += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert(new_cursor.range.max >= new_cursor.range.min);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (Int i = 0; i < carets->len; i += 1) {
|
for (Int i = 0; i < carets->len; i += 1) {
|
||||||
Caret &old_cursor = carets->data[i];
|
carets->data[i] = new_carets[i];
|
||||||
Caret &new_cursor = new_carets.data[i];
|
if (kill_selection) {
|
||||||
|
carets->data[i].range.max = carets->data[i].range.min;
|
||||||
if (old_cursor.range.min == edit.range.min) {
|
|
||||||
new_cursor.range.min += insert_size;
|
|
||||||
} else if (old_cursor.range.min > edit.range.min) {
|
|
||||||
new_cursor.range.min += offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (old_cursor.range.max == edit.range.max) {
|
|
||||||
new_cursor.range.max += insert_size;
|
|
||||||
} else if (old_cursor.range.max > edit.range.max) {
|
|
||||||
new_cursor.range.max += offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert(new_cursor.range.max >= new_cursor.range.min);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Int i = 0; i < carets->len; i += 1) {
|
|
||||||
carets->data[i] = new_carets[i];
|
|
||||||
if (kill_selection) {
|
|
||||||
carets->data[i].range.max = carets->data[i].range.min;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1134,6 +1157,7 @@ API void InitBuffer(Allocator allocator, Buffer *buffer, BufferID id = {}, Strin
|
|||||||
API void DeinitBuffer(Buffer *buffer) {
|
API void DeinitBuffer(Buffer *buffer) {
|
||||||
Allocator allocator = buffer->line_starts.allocator;
|
Allocator allocator = buffer->line_starts.allocator;
|
||||||
Dealloc(allocator, buffer->data);
|
Dealloc(allocator, buffer->data);
|
||||||
|
Dealloc(&buffer->commands);
|
||||||
Dealloc(&buffer->line_starts);
|
Dealloc(&buffer->line_starts);
|
||||||
DeallocHistoryArray(&buffer->undo_stack);
|
DeallocHistoryArray(&buffer->undo_stack);
|
||||||
DeallocHistoryArray(&buffer->redo_stack);
|
DeallocHistoryArray(&buffer->redo_stack);
|
||||||
@@ -1217,7 +1241,7 @@ void RunBufferTest() {
|
|||||||
Assert(buffer.data[15] == L'\n');
|
Assert(buffer.data[15] == L'\n');
|
||||||
Assert(buffer.data[16] == L't');
|
Assert(buffer.data[16] == L't');
|
||||||
|
|
||||||
RawReplaceText(&buffer, GetBufferBeginAsRange(&buffer), u"Things as is\nand stuff\n");
|
RawReplaceText(&buffer, {}, u"Things as is\nand stuff\n");
|
||||||
Assert(buffer.line_starts.len == 4);
|
Assert(buffer.line_starts.len == 4);
|
||||||
Assert(PosToLine(&buffer, 12) == 0);
|
Assert(PosToLine(&buffer, 12) == 0);
|
||||||
Assert(buffer.data[12] == L'\n');
|
Assert(buffer.data[12] == L'\n');
|
||||||
@@ -1232,7 +1256,7 @@ void RunBufferTest() {
|
|||||||
Assert(PosToLine(&buffer, 39) == 3);
|
Assert(PosToLine(&buffer, 39) == 3);
|
||||||
Assert(buffer.data[39] == L't');
|
Assert(buffer.data[39] == L't');
|
||||||
|
|
||||||
RawReplaceText(&buffer, GetBufferBeginAsRange(&buffer), u"a");
|
RawReplaceText(&buffer, {}, u"a");
|
||||||
Assert(buffer.line_starts.len == 4);
|
Assert(buffer.line_starts.len == 4);
|
||||||
Assert(PosToLine(&buffer, 13) == 0);
|
Assert(PosToLine(&buffer, 13) == 0);
|
||||||
Assert(PosToLine(&buffer, 14) == 1);
|
Assert(PosToLine(&buffer, 14) == 1);
|
||||||
@@ -1301,7 +1325,7 @@ void RunBufferTest() {
|
|||||||
AddEdit(&edits, {0, 7}, u"t");
|
AddEdit(&edits, {0, 7}, u"t");
|
||||||
AddEdit(&edits, {8, 9}, u"T");
|
AddEdit(&edits, {8, 9}, u"T");
|
||||||
AddEdit(&edits, GetBufferEndAsRange(&buffer), u"\nnewThing");
|
AddEdit(&edits, GetBufferEndAsRange(&buffer), u"\nnewThing");
|
||||||
EndEdit(&buffer, &edits, &carets, KILL_SELECTION);
|
EndEdit(&buffer, &edits, &carets, EndEdit_KillSelection);
|
||||||
String16 s = GetString(&buffer);
|
String16 s = GetString(&buffer);
|
||||||
Assert(s == u"t\nThings\nnewThing");
|
Assert(s == u"t\nThings\nnewThing");
|
||||||
DeinitBuffer(&buffer);
|
DeinitBuffer(&buffer);
|
||||||
@@ -1325,7 +1349,7 @@ void RunBufferTest() {
|
|||||||
AddEdit(&edits, {0, 7}, u"t");
|
AddEdit(&edits, {0, 7}, u"t");
|
||||||
AddEdit(&edits, {8, 9}, u"T");
|
AddEdit(&edits, {8, 9}, u"T");
|
||||||
AddEdit(&edits, GetBufferEndAsRange(&buffer), u"\nnewThing");
|
AddEdit(&edits, GetBufferEndAsRange(&buffer), u"\nnewThing");
|
||||||
EndEdit(&buffer, &edits, &carets, KILL_SELECTION);
|
EndEdit(&buffer, &edits, &carets, EndEdit_KillSelection);
|
||||||
String16 s = GetString(&buffer);
|
String16 s = GetString(&buffer);
|
||||||
Assert(s == u"t\nThings\nnewThing");
|
Assert(s == u"t\nThings\nnewThing");
|
||||||
Assert(buffer.line_starts.len == 0);
|
Assert(buffer.line_starts.len == 0);
|
||||||
@@ -1346,14 +1370,28 @@ BufferID AllocBufferID(Buffer *buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Buffer *GetBuffer(BufferID id, Buffer *default_buffer = Buffers[0]) {
|
Buffer *GetBuffer(BufferID id, Buffer *default_buffer = Buffers[0]) {
|
||||||
For(Buffers) {
|
Int left = 0;
|
||||||
if (it->id == id) return it;
|
Int right = Buffers.len - 1;
|
||||||
|
Buffer *result = default_buffer;
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
Int mid = left + (right - left) / 2;
|
||||||
|
Buffer *it = Buffers[mid];
|
||||||
|
if (it->id == id) {
|
||||||
|
result = it;
|
||||||
|
break;
|
||||||
|
} else if (it->id.id < id.id) {
|
||||||
|
left = mid + 1;
|
||||||
|
} else {
|
||||||
|
right = mid - 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return default_buffer;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer *GetBuffer(String name, Buffer *default_buffer = Buffers[0]) {
|
Buffer *GetBuffer(String name, Buffer *default_buffer = Buffers[0]) {
|
||||||
For(Buffers) {
|
For (Buffers) {
|
||||||
if (it->name == name) return it;
|
if (it->name == name) return it;
|
||||||
}
|
}
|
||||||
return default_buffer;
|
return default_buffer;
|
||||||
@@ -1375,7 +1413,7 @@ void Close(BufferID id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer *CreateBuffer(Allocator allocator, String name, Int size) {
|
Buffer *CreateBuffer(Allocator allocator, String name, Int size = 4096) {
|
||||||
Buffer *result = AllocBuffer(allocator, name, size);
|
Buffer *result = AllocBuffer(allocator, name, size);
|
||||||
Add(&Buffers, result);
|
Add(&Buffers, result);
|
||||||
return result;
|
return result;
|
||||||
@@ -1396,29 +1434,6 @@ String GetUniqueBufferName(String working_dir, String prepend_name, String exten
|
|||||||
return buffer_name;
|
return buffer_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitBuffers() {
|
|
||||||
Allocator sys_allocator = GetSystemAllocator();
|
|
||||||
Scratch scratch;
|
|
||||||
Buffer *null_buffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "logs", ""));
|
|
||||||
null_buffer->special = true;
|
|
||||||
View *null_view = CreateView(null_buffer->id);
|
|
||||||
null_view->special = true;
|
|
||||||
Assert(null_buffer->id == NullBufferID && null_view->id == NullViewID);
|
|
||||||
TraceBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "trace"));
|
|
||||||
TraceBuffer->special = true;
|
|
||||||
TraceBuffer->no_history = true;
|
|
||||||
GCInfoBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "gc"));
|
|
||||||
GCInfoBuffer->special = true;
|
|
||||||
GCInfoBuffer->no_history = true;
|
|
||||||
EventBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "events"));
|
|
||||||
EventBuffer->no_history = true;
|
|
||||||
EventBuffer->special = true;
|
|
||||||
Buffer *search_project = CreateBuffer(sys_allocator, GetUniqueBufferName(WorkDir, "search_project"));
|
|
||||||
search_project->no_history = true;
|
|
||||||
search_project->special = true;
|
|
||||||
SearchProjectBufferID = search_project->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
Int ConvertUTF8ToUTF16UnixLine(String string, char16_t *buffer, Int buffer_cap) {
|
Int ConvertUTF8ToUTF16UnixLine(String string, char16_t *buffer, Int buffer_cap) {
|
||||||
if (string.len == 0) {
|
if (string.len == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1433,7 +1448,7 @@ Int ConvertUTF8ToUTF16UnixLine(String string, char16_t *buffer, Int buffer_cap)
|
|||||||
}
|
}
|
||||||
if (string.data[i] == '\t') {
|
if (string.data[i] == '\t') {
|
||||||
// @WARNING: DONT INCREASE THE SIZE CARELESSLY, WE NEED TO ADJUST BUFFER SIZE
|
// @WARNING: DONT INCREASE THE SIZE CARELESSLY, WE NEED TO ADJUST BUFFER SIZE
|
||||||
for (Int i = 0; i < 4; i += 1) buffer[buffer_len++] = u' ';
|
for (Int ii = 0; ii < 4; ii += 1) buffer[buffer_len++] = u' ';
|
||||||
i += 1;
|
i += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1468,6 +1483,15 @@ String16 ToUnixString16(Allocator allocator, String string_) {
|
|||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function as name suggests tries to open a buffer,
|
||||||
|
// there is no name resolution here, path should already be resolved etc.
|
||||||
|
//
|
||||||
|
// 1. It tries to find an already open buffer
|
||||||
|
// 2. Returns a buffer if the file doesn't exist (even if a directory doesn't exist)
|
||||||
|
// - This is a worry for later time, also we want to handle weird names and so on
|
||||||
|
// 3. If file exists we read it, convert to utf16, tabs to spaces etc.
|
||||||
|
//
|
||||||
|
// We don't really care about opening buffers that don't have proper paths
|
||||||
Buffer *BufferOpenFile(String path) {
|
Buffer *BufferOpenFile(String path) {
|
||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
Allocator sys_allocator = GetSystemAllocator();
|
Allocator sys_allocator = GetSystemAllocator();
|
||||||
@@ -1483,8 +1507,10 @@ Buffer *BufferOpenFile(String path) {
|
|||||||
buffer = CreateBuffer(sys_allocator, path);
|
buffer = CreateBuffer(sys_allocator, path);
|
||||||
} else if (IsDir(path)) {
|
} else if (IsDir(path)) {
|
||||||
buffer = CreateBuffer(sys_allocator, path);
|
buffer = CreateBuffer(sys_allocator, path);
|
||||||
|
#if PLUGIN_DIRECTORY_NAVIGATION
|
||||||
buffer->is_dir = true;
|
buffer->is_dir = true;
|
||||||
buffer->temp = true;
|
buffer->temp = true;
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
String string = ReadFile(scratch, path);
|
String string = ReadFile(scratch, path);
|
||||||
buffer = CreateBuffer(sys_allocator, path, string.len * 4 + 4096);
|
buffer = CreateBuffer(sys_allocator, path, string.len * 4 + 4096);
|
||||||
@@ -1496,28 +1522,12 @@ Buffer *BufferOpenFile(String path) {
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BufferIsReferenced(BufferID buffer_id) {
|
|
||||||
if (buffer_id == NullBufferID) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FindView(buffer_id, NULL)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReopenBuffer(Buffer *buffer) {
|
void ReopenBuffer(Buffer *buffer) {
|
||||||
Scratch scratch;
|
Scratch scratch;
|
||||||
if (buffer->is_dir) {
|
|
||||||
ResetBuffer(buffer);
|
#if PLUGIN_DIRECTORY_NAVIGATION
|
||||||
RawAppendf(buffer, "..\n");
|
if (buffer->is_dir) { InsertDirectoryNavigation(buffer); return; }
|
||||||
for (FileIter it = IterateFiles(scratch, buffer->name); IsValid(it); Advance(&it)) {
|
#endif
|
||||||
RawAppendf(buffer, "%S\n", it.filename);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String string = ReadFile(scratch, buffer->name);
|
String string = ReadFile(scratch, buffer->name);
|
||||||
if (string.len == 0) {
|
if (string.len == 0) {
|
||||||
@@ -1531,7 +1541,13 @@ void ReopenBuffer(Buffer *buffer) {
|
|||||||
buffer->dirty = false;
|
buffer->dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicSaveBuffer(Buffer *buffer) {
|
void SaveBuffer(Buffer *buffer) {
|
||||||
|
ProfileFunction();
|
||||||
|
|
||||||
|
if (TrimTrailingWhitespace) {
|
||||||
|
TrimWhitespace(buffer, false);
|
||||||
|
}
|
||||||
|
|
||||||
Scratch scratch;
|
Scratch scratch;
|
||||||
String string = AllocCharString(scratch, buffer);
|
String string = AllocCharString(scratch, buffer);
|
||||||
bool success = WriteFile(buffer->name, string);
|
bool success = WriteFile(buffer->name, string);
|
||||||
@@ -1541,14 +1557,37 @@ void BasicSaveBuffer(Buffer *buffer) {
|
|||||||
buffer->dirty = false;
|
buffer->dirty = false;
|
||||||
buffer->temp = false;
|
buffer->temp = false;
|
||||||
} else {
|
} else {
|
||||||
ReportWarningf("Failed to save file with name: %S", buffer->name);
|
ReportErrorf("Failed to save file with name: %S", buffer->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveBuffer(Buffer *buffer) {
|
void SaveAll() {
|
||||||
if (TrimTrailingWhitespace) {
|
For(Buffers) {
|
||||||
TrimWhitespace(buffer);
|
// NOTE: file_mod_time is only set when buffer got read or written to disk already so should be saved
|
||||||
|
if (it->file_mod_time && it->dirty) {
|
||||||
|
SaveBuffer(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String GetDirectory(Buffer *buffer) {
|
||||||
|
#if PLUGIN_DIRECTORY_NAVIGATION
|
||||||
|
if (buffer->is_dir) {
|
||||||
|
return buffer->name;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
String result = ChopLastSlash(buffer->name);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TryReopeningWhenModified(Buffer *it) {
|
||||||
|
if (it->file_mod_time) {
|
||||||
|
int64_t new_file_mod_time = GetFileModTime(it->name);
|
||||||
|
if (it->file_mod_time != new_file_mod_time) {
|
||||||
|
it->changed_on_disk = true;
|
||||||
|
if (it->dirty == false) {
|
||||||
|
ReopenBuffer(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicSaveBuffer(buffer);
|
|
||||||
}
|
}
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
struct Buffer;
|
|
||||||
struct BufferID { Int id; Buffer *o; };
|
|
||||||
union Range { struct { Int min; Int max; }; Int e[2]; };
|
|
||||||
struct Caret { union { Range range; Int pos[2]; }; Int ifront;};
|
|
||||||
struct XY { Int col; Int line; };
|
|
||||||
|
|
||||||
struct Edit {
|
|
||||||
Range range;
|
|
||||||
String16 string;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HistoryEntry {
|
|
||||||
Array<Edit> edits;
|
|
||||||
Array<Caret> carets;
|
|
||||||
double time;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Buffer {
|
|
||||||
BufferID id;
|
|
||||||
String name;
|
|
||||||
Int begin_frame_change_id;
|
|
||||||
Int change_id;
|
|
||||||
Int user_change_id;
|
|
||||||
Int file_mod_time;
|
|
||||||
|
|
||||||
union {
|
|
||||||
U16 *data;
|
|
||||||
char16_t*str;
|
|
||||||
};
|
|
||||||
Int len;
|
|
||||||
Int cap;
|
|
||||||
Array<Int> line_starts;
|
|
||||||
|
|
||||||
Array<HistoryEntry> undo_stack;
|
|
||||||
Array<HistoryEntry> redo_stack;
|
|
||||||
int edit_phase;
|
|
||||||
struct {
|
|
||||||
unsigned no_history : 1;
|
|
||||||
unsigned no_line_starts : 1;
|
|
||||||
unsigned dirty : 1;
|
|
||||||
unsigned changed_on_disk : 1;
|
|
||||||
unsigned temp : 1;
|
|
||||||
unsigned dont_try_to_save_in_bulk_ops : 1;
|
|
||||||
unsigned close : 1;
|
|
||||||
unsigned special : 1;
|
|
||||||
unsigned is_dir : 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FuzzyPair {
|
|
||||||
int32_t index;
|
|
||||||
float rating;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum {
|
|
||||||
KILL_SELECTION = 1,
|
|
||||||
};
|
|
||||||
constexpr Int LAST_LINE = INT64_MAX;
|
|
||||||
|
|
||||||
// @todo: buffer init, deinit
|
|
||||||
API Range MakeRange(Int a, Int b);
|
|
||||||
API Range MakeRange(Int a);
|
|
||||||
API Int GetSize(Range range);
|
|
||||||
API bool AreEqual(Range a, Range b);
|
|
||||||
API bool AreOverlapping(Range a, Range b);
|
|
||||||
API bool InBounds(Range range, Int pos);
|
|
||||||
API Range operator-(Range a, Int value);
|
|
||||||
API Range operator-=(Range &range, Int value);
|
|
||||||
API Range operator+(Range a, Int value);
|
|
||||||
API Range operator+=(Range &range, Int value);
|
|
||||||
API Range GetBufferEndAsRange(Buffer *buffer);
|
|
||||||
API Range GetBufferBeginAsRange(Buffer *buffer);
|
|
||||||
API Range GetRange(Buffer *buffer);
|
|
||||||
API Int Clamp(const Buffer *buffer, Int pos);
|
|
||||||
API Range Clamp(const Buffer *buffer, Range range);
|
|
||||||
|
|
||||||
API Int GetFront(Caret caret);
|
|
||||||
API Int GetBack(Caret caret);
|
|
||||||
API Caret MakeCaret(Int pos);
|
|
||||||
API Caret MakeCaret(Int front, Int back);
|
|
||||||
API Caret SetBack(Caret caret, Int back);
|
|
||||||
API Caret SetFront(Caret caret, Int front);
|
|
||||||
API Caret SetFrontWithAnchor(Caret caret, Caret anchor, Int p);
|
|
||||||
API bool AreEqual(Caret a, Caret b);
|
|
||||||
API bool AreOverlapping(Caret a, Caret b);
|
|
||||||
|
|
||||||
API Range GetLineRange(Buffer *buffer, Int line, Int *end_of_buffer = NULL);
|
|
||||||
API Range GetLineRangeWithoutNL(Buffer *buffer, Int line);
|
|
||||||
API String16 GetString(Buffer *buffer, Range range = {0, INT64_MAX});
|
|
||||||
API String AllocCharString(Allocator allocator, Buffer *buffer, Range range = {0, INT64_MAX});
|
|
||||||
API String16 GetLineString(Buffer *buffer, Int line, Int *end_of_buffer = NULL);
|
|
||||||
API String16 GetLineStringWithoutNL(Buffer *buffer, Int line);
|
|
||||||
|
|
||||||
API XY XYLine(Int line);
|
|
||||||
API Int LastLine(Buffer *buffer);
|
|
||||||
API Int PosToLine(Buffer *buffer, Int pos);
|
|
||||||
API XY PosToXY(Buffer *buffer, Int pos);
|
|
||||||
API Int XYToPos(Buffer *buffer, XY xy);
|
|
||||||
API Int XYToPosWithoutNL(Buffer *buffer, XY xy);
|
|
||||||
API Int XYToPosErrorOutOfBounds(Buffer *buffer, XY xy);
|
|
||||||
|
|
||||||
API char16_t GetChar(Buffer *buffer, Int pos);
|
|
||||||
API bool InBounds(const Buffer *buffer, Int pos);
|
|
||||||
API Int GetWordStart(Buffer *buffer, Int pos);
|
|
||||||
API Int GetWordEnd(Buffer *buffer, Int pos);
|
|
||||||
API bool IsLoadWord(char16_t w);
|
|
||||||
API Int GetLoadWordStart(Buffer *buffer, Int pos);
|
|
||||||
API Int GetLoadWordEnd(Buffer *buffer, Int pos);
|
|
||||||
API Range EncloseLoadWord(Buffer *buffer, Int pos);
|
|
||||||
API Int GetNextWordEnd(Buffer *buffer, Int pos);
|
|
||||||
API Int GetPrevWordStart(Buffer *buffer, Int pos);
|
|
||||||
API Int GetLineStart(Buffer *buffer, Int pos);
|
|
||||||
API Int GetLineEnd(Buffer *buffer, Int pos);
|
|
||||||
API Int GetFullLineStart(Buffer *buffer, Int pos);
|
|
||||||
API Int GetFullLineEnd(Buffer *buffer, Int pos, Int *eof = NULL);
|
|
||||||
API Int GetBufferEnd(Buffer *buffer);
|
|
||||||
API Int GetBufferStart(Buffer *buffer);
|
|
||||||
API Int GetNextEmptyLineStart(Buffer *buffer, Int pos);
|
|
||||||
API Int GetPrevEmptyLineStart(Buffer *buffer, Int pos);
|
|
||||||
API Range EncloseWord(Buffer *buffer, Int pos);
|
|
||||||
API Int SkipSpaces(Buffer *buffer, Int seek);
|
|
||||||
API Range EncloseLine(Buffer *buffer, Int pos);
|
|
||||||
API Range EncloseFullLine(Buffer *buffer, Int pos);
|
|
||||||
API Int OffsetByLine(Buffer *buffer, Int pos, Int line_offset);
|
|
||||||
API Int GetNextChar(Buffer *buffer, Int pos);
|
|
||||||
API Int GetPrevChar(Buffer *buffer, Int pos);
|
|
||||||
API Int GetLineIndent(Buffer *buffer, Int line);
|
|
||||||
API Int GetIndentAtPos(Buffer *buffer, Int pos);
|
|
||||||
API Range GetIndentRangeAtPos(Buffer *buffer, Int pos);
|
|
||||||
|
|
||||||
API Int FindRangeByPos(Array<Range> *ranges, Int pos);
|
|
||||||
|
|
||||||
// These don't handle history, just raw operations on buffer memory
|
|
||||||
API void RawReplaceText(Buffer *buffer, Range range, String16 string);
|
|
||||||
API void RawAppend(Buffer *buffer, String16 string);
|
|
||||||
API void RawAppend(Buffer *buffer, String string);
|
|
||||||
API void RawAppendf(Buffer *buffer, const char *fmt, ...);
|
|
||||||
|
|
||||||
// We can invoke SaveCaretHistoryBeforeBeginEdit(which is basically BeginEdit with
|
|
||||||
// different name before caret altering commands to save caret history
|
|
||||||
// and then call some editing command to edit which is not going to save carets
|
|
||||||
API void SaveCaretHistoryBeforeBeginEdit(Buffer *buffer, Array<Caret> &carets);
|
|
||||||
API Array<Edit> BeginEdit(Allocator allocator, Buffer *buffer, Array<Caret> &carets);
|
|
||||||
API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool kill_selection);
|
|
||||||
API void AddEdit(Array<Edit> *e, Range range, String16 string);
|
|
||||||
|
|
||||||
// Merge carets that overlap, this needs to be handled before any edits to
|
|
||||||
// make sure overlapping edits won't happen.
|
|
||||||
//
|
|
||||||
// mouse_selection_anchor is special case for mouse handling !
|
|
||||||
API void MergeCarets(Buffer *buffer, Array<Caret> *carets);
|
|
||||||
|
|
||||||
// Adjusts caret copies after edit to make them not move after for example
|
|
||||||
// a bar modification. Sometimes works, sometimes doesn't, depends, not an all solving tool.
|
|
||||||
// For example in case of ReopenBuffer, when we select and replace entire buffer, it didn't quite work.
|
|
||||||
API void AdjustCarets(Array<Edit> edits, Array<Caret> *carets);
|
|
||||||
API void AssertRanges(Array<Caret> carets);
|
|
||||||
|
|
||||||
API void RedoEdit(Buffer *buffer, Array<Caret> *carets);
|
|
||||||
API void UndoEdit(Buffer *buffer, Array<Caret> *carets);
|
|
||||||
API void ResetHistory(Buffer *buffer);
|
|
||||||
|
|
||||||
API void DeallocHistoryArray(Array<HistoryEntry> *entries);
|
|
||||||
API void DeallocHistoryEntries(Array<HistoryEntry> *entries);
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -40,9 +40,13 @@ void ClipboardCopy(View *view) {
|
|||||||
// First, if there is no selection - select the entire line
|
// First, if there is no selection - select the entire line
|
||||||
For(view->carets) {
|
For(view->carets) {
|
||||||
if (GetSize(it.range) == 0) {
|
if (GetSize(it.range) == 0) {
|
||||||
Int line = PosToLine(buffer, it.range.min);
|
Int line = PosToLine(buffer, it.range.min);
|
||||||
Range line_range = GetLineRange(buffer, line);
|
Int eof = 0;
|
||||||
it.range = line_range;
|
Range line_range = GetLineRange(buffer, line, &eof);
|
||||||
|
it.range = line_range;
|
||||||
|
if (eof) {
|
||||||
|
it.range.min = ClampBottom(0ll, it.range.min - 1ll);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +76,7 @@ void ClipboardPaste(View *view) {
|
|||||||
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
|
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
|
||||||
MergeCarets(buffer, &view->carets);
|
MergeCarets(buffer, &view->carets);
|
||||||
For(view->carets) AddEdit(&edits, it.range, string);
|
For(view->carets) AddEdit(&edits, it.range, string);
|
||||||
EndEdit(buffer, &edits, &view->carets, KILL_SELECTION);
|
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,26 +84,26 @@ void ClipboardPaste(View *view) {
|
|||||||
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
|
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
|
||||||
MergeCarets(buffer, &view->carets);
|
MergeCarets(buffer, &view->carets);
|
||||||
for (int64_t i = 0; i < view->carets.len; i += 1) {
|
for (int64_t i = 0; i < view->carets.len; i += 1) {
|
||||||
String16 string = SavedClipboardCarets[i];
|
String16 saved_string = SavedClipboardCarets[i];
|
||||||
Caret &it = view->carets[i];
|
Caret &it = view->carets[i];
|
||||||
AddEdit(&edits, it.range, string);
|
AddEdit(&edits, it.range, saved_string);
|
||||||
}
|
}
|
||||||
EndEdit(buffer, &edits, &view->carets, KILL_SELECTION);
|
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMD_Paste() {
|
void CMD_Paste() {
|
||||||
BSet active = GetBSet(ActiveWindowID);
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
ClipboardPaste(active.view);
|
ClipboardPaste(active.view);
|
||||||
} RegisterCommand(CMD_Paste, "ctrl-v");
|
} RegisterCommand(CMD_Paste, "ctrl-v", "Paste the content of system clipboard at caret");
|
||||||
|
|
||||||
void CMD_Copy() {
|
void CMD_Copy() {
|
||||||
BSet active = GetBSet(ActiveWindowID);
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
ClipboardCopy(active.view);
|
ClipboardCopy(active.view);
|
||||||
} RegisterCommand(CMD_Copy, "ctrl-c");
|
} RegisterCommand(CMD_Copy, "ctrl-c", "Copy currently selected content to system clipboard");
|
||||||
|
|
||||||
void CMD_Cut() {
|
void CMD_Cut() {
|
||||||
BSet active = GetBSet(ActiveWindowID);
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets);
|
SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets);
|
||||||
ClipboardCopy(active.view);
|
ClipboardCopy(active.view);
|
||||||
Replace(active.view, u"");
|
Replace(active.view, u"");
|
||||||
} RegisterCommand(CMD_Cut, "ctrl-x");
|
} RegisterCommand(CMD_Cut, "ctrl-x", "Copy and delete currently selected content to system clipboard");
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
struct Lexer {
|
|
||||||
Allocator allocator;
|
|
||||||
char *at;
|
|
||||||
char *start;
|
|
||||||
char *end;
|
|
||||||
char *name;
|
|
||||||
int line, column;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum TriggerKind {
|
enum TriggerKind {
|
||||||
TriggerKind_Error,
|
TriggerKind_Error,
|
||||||
TriggerKind_Key,
|
TriggerKind_Key,
|
||||||
@@ -28,35 +19,13 @@ struct Trigger {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
void Advance(Lexer *lex) {
|
struct CachedTrigger {
|
||||||
if (lex->at < lex->end) {
|
Trigger *trigger;
|
||||||
if (lex->at[0] == '\n') {
|
String key;
|
||||||
lex->line += 1;
|
};
|
||||||
lex->column = 0;
|
Array<CachedTrigger> CachedTriggers;
|
||||||
} else {
|
|
||||||
lex->column += 1;
|
|
||||||
}
|
|
||||||
lex->at += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Advance(Lexer *lex, int n) {
|
void EatWhitespaceEx(Lexer *lex) {
|
||||||
for (int i = 0; i < n; i += 1) Advance(lex);
|
|
||||||
}
|
|
||||||
|
|
||||||
char At(Lexer *lex) {
|
|
||||||
if (lex->at < lex->end) {
|
|
||||||
return lex->at[0];
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
String AsString(Lexer *lex) {
|
|
||||||
String result = {lex->at, lex->end - lex->at};
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EatWhitespace(Lexer *lex) {
|
|
||||||
while (At(lex) != '\n' && IsWhitespace(At(lex))) {
|
while (At(lex) != '\n' && IsWhitespace(At(lex))) {
|
||||||
Advance(lex);
|
Advance(lex);
|
||||||
}
|
}
|
||||||
@@ -74,7 +43,7 @@ Trigger *TriggerBinary(Lexer *lex, Trigger *left, Trigger *right, int key) {
|
|||||||
Trigger *ParseKeyAtom(Lexer *lex) {
|
Trigger *ParseKeyAtom(Lexer *lex) {
|
||||||
Trigger *result = AllocType(lex->allocator, Trigger);
|
Trigger *result = AllocType(lex->allocator, Trigger);
|
||||||
result->kind = TriggerKind_Key;
|
result->kind = TriggerKind_Key;
|
||||||
EatWhitespace(lex);
|
EatWhitespaceEx(lex);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
String lex_string = AsString(lex);
|
String lex_string = AsString(lex);
|
||||||
if (StartsWith(lex_string, "ctrl")) {
|
if (StartsWith(lex_string, "ctrl")) {
|
||||||
@@ -112,12 +81,12 @@ Trigger *ParseKeyAtom(Lexer *lex) {
|
|||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
result->kind = TriggerKind_Error;
|
result->kind = TriggerKind_Error;
|
||||||
ReportErrorf("%s:%d:%d: Failed to parse key trigger, unexpected identifier: '%d'", lex->name, lex->line, lex->column, result->key);
|
ReportErrorf("%s:%d:%d: Failed to parse key trigger, unexpected identifier: '%d'", lex->file, lex->line, lex->column, result->key);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result->kind = TriggerKind_Error;
|
result->kind = TriggerKind_Error;
|
||||||
ReportErrorf("%s:%d:%d: Failed to parse key trigger, unexpected character: '%c'", lex->name, lex->line, lex->column, At(lex));
|
ReportErrorf("%s:%d:%d: Failed to parse key trigger, unexpected character: '%c'", lex->file, lex->line, lex->column, At(lex));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,21 +101,21 @@ Trigger *ParseKeyAtom(Lexer *lex) {
|
|||||||
|
|
||||||
Trigger *ParseKeyChord(Lexer *lex) {
|
Trigger *ParseKeyChord(Lexer *lex) {
|
||||||
Trigger *left = ParseKeyAtom(lex);
|
Trigger *left = ParseKeyAtom(lex);
|
||||||
EatWhitespace(lex);
|
EatWhitespaceEx(lex);
|
||||||
while (IsAlphanumeric(At(lex))) {
|
while (IsAlphanumeric(At(lex))) {
|
||||||
left = TriggerBinary(lex, left, ParseKeyChord(lex), ' ');
|
left = TriggerBinary(lex, left, ParseKeyChord(lex), ' ');
|
||||||
EatWhitespace(lex);
|
EatWhitespaceEx(lex);
|
||||||
}
|
}
|
||||||
return left;
|
return left;
|
||||||
}
|
}
|
||||||
|
|
||||||
Trigger *ParseKeyOr(Lexer *lex) {
|
Trigger *ParseKeyOr(Lexer *lex) {
|
||||||
Trigger *left = ParseKeyChord(lex);
|
Trigger *left = ParseKeyChord(lex);
|
||||||
EatWhitespace(lex);
|
EatWhitespaceEx(lex);
|
||||||
while (At(lex) == '|') {
|
while (At(lex) == '|') {
|
||||||
Advance(lex);
|
Advance(lex);
|
||||||
left = TriggerBinary(lex, left, ParseKeyOr(lex), '|');
|
left = TriggerBinary(lex, left, ParseKeyOr(lex), '|');
|
||||||
EatWhitespace(lex);
|
EatWhitespaceEx(lex);
|
||||||
}
|
}
|
||||||
return left;
|
return left;
|
||||||
}
|
}
|
||||||
@@ -168,12 +137,6 @@ Trigger *ParseKey(Allocator allocator, String key, char *debug_name) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CachedTrigger {
|
|
||||||
Trigger *trigger;
|
|
||||||
String key;
|
|
||||||
};
|
|
||||||
Array<CachedTrigger> CachedTriggers;
|
|
||||||
|
|
||||||
Trigger *ParseKeyCached(String key) {
|
Trigger *ParseKeyCached(String key) {
|
||||||
key = Trim(key);
|
key = Trim(key);
|
||||||
if (key.len == 0) {
|
if (key.len == 0) {
|
||||||
@@ -227,7 +190,7 @@ void TestParser() {
|
|||||||
Scratch scratch;
|
Scratch scratch;
|
||||||
{
|
{
|
||||||
char *cmd = "ctrl-b";
|
char *cmd = "ctrl-b";
|
||||||
Lexer base_lex = {scratch, cmd, cmd, cmd + strlen(cmd), "keybinding"};
|
Lexer base_lex = {scratch, cmd, cmd, cmd + Strlen(cmd), "keybinding"};
|
||||||
Trigger *trigger = ParseKeyCatchAll(&base_lex);
|
Trigger *trigger = ParseKeyCatchAll(&base_lex);
|
||||||
Assert(trigger->kind == TriggerKind_Key);
|
Assert(trigger->kind == TriggerKind_Key);
|
||||||
Assert(trigger->key == SDLK_B);
|
Assert(trigger->key == SDLK_B);
|
||||||
@@ -237,7 +200,7 @@ void TestParser() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
char *cmd = "ctrl-b shift-ctrl-a";
|
char *cmd = "ctrl-b shift-ctrl-a";
|
||||||
Lexer base_lex = {scratch, cmd, cmd, cmd + strlen(cmd), "keybinding"};
|
Lexer base_lex = {scratch, cmd, cmd, cmd + Strlen(cmd), "keybinding"};
|
||||||
Trigger *trigger = ParseKeyCatchAll(&base_lex);
|
Trigger *trigger = ParseKeyCatchAll(&base_lex);
|
||||||
Assert(trigger->kind == TriggerKind_Binary);
|
Assert(trigger->kind == TriggerKind_Binary);
|
||||||
Assert(trigger->key == ' ');
|
Assert(trigger->key == ' ');
|
||||||
@@ -253,7 +216,7 @@ void TestParser() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
char *cmd = "ctrl-b shift-ctrl-a | ctrl-c | ctrl-d";
|
char *cmd = "ctrl-b shift-ctrl-a | ctrl-c | ctrl-d";
|
||||||
Lexer base_lex = {scratch, cmd, cmd, cmd + strlen(cmd), "keybinding"};
|
Lexer base_lex = {scratch, cmd, cmd, cmd + Strlen(cmd), "keybinding"};
|
||||||
Trigger *trigger = ParseKeyCatchAll(&base_lex);
|
Trigger *trigger = ParseKeyCatchAll(&base_lex);
|
||||||
Assert(trigger->kind == TriggerKind_Binary);
|
Assert(trigger->kind == TriggerKind_Binary);
|
||||||
Assert(trigger->key == '|');
|
Assert(trigger->key == '|');
|
||||||
@@ -283,71 +246,3 @@ void TestParser() {
|
|||||||
Assert(!ok);
|
Assert(!ok);
|
||||||
}
|
}
|
||||||
} RegisterFunction(&TestFunctions, TestParser);
|
} RegisterFunction(&TestFunctions, TestParser);
|
||||||
|
|
||||||
void CMD_OpenConfig() {
|
|
||||||
Buffer *buffer = GetBuffer(GlobalConfigBufferID);
|
|
||||||
Open(buffer->name);
|
|
||||||
} RegisterCommand(CMD_OpenConfig, "", "Open the global config file");
|
|
||||||
|
|
||||||
void CMD_OpenConfigOptions() {
|
|
||||||
BSet main = GetBSet(PrimaryWindowID);
|
|
||||||
JumpTempBuffer(&main);
|
|
||||||
NextActiveWindowID = main.window->id;
|
|
||||||
For (Variables) {
|
|
||||||
RawAppendf(main.buffer, "\n:Set %-50S ", it.name);
|
|
||||||
switch(it.type) {
|
|
||||||
case VariableType_Color: RawAppendf(main.buffer, "%x", it.color->value); break;
|
|
||||||
case VariableType_String: RawAppendf(main.buffer, "'%S'", *it.string); break;
|
|
||||||
case VariableType_Int: RawAppendf(main.buffer, "%lld", (long long)*it.i); break;
|
|
||||||
case VariableType_Float: RawAppendf(main.buffer, "%f", *it.f); break;
|
|
||||||
default: InvalidCodepath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
For (CommandFunctions) {
|
|
||||||
RawAppendf(main.buffer, "\n:Set %-50S '%S'", it.name, it.binding);
|
|
||||||
}
|
|
||||||
} RegisterCommand(CMD_OpenConfigOptions, "", "List available variables and associated documentation inside the command window");
|
|
||||||
|
|
||||||
void EvalCommandsLineByLine(BSet set) {
|
|
||||||
WindowID save_last = PrimaryWindowID;
|
|
||||||
WindowID save_active = ActiveWindowID;
|
|
||||||
WindowID save_next = NextActiveWindowID;
|
|
||||||
Caret save_caret = set.view->carets[0];
|
|
||||||
ActiveWindowID = set.window->id;
|
|
||||||
PrimaryWindowID = set.window->id;
|
|
||||||
NextActiveWindowID = set.window->id;
|
|
||||||
for (Int i = 0; i < set.buffer->line_starts.len; i += 1) {
|
|
||||||
Range range = GetLineRangeWithoutNL(set.buffer, i);
|
|
||||||
String16 string = GetString(set.buffer, range);
|
|
||||||
string = Trim(string);
|
|
||||||
if (string.len == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (StartsWith(string, u"//")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Open(string);
|
|
||||||
}
|
|
||||||
set.view->carets[0] = save_caret;
|
|
||||||
PrimaryWindowID = save_last;
|
|
||||||
ActiveWindowID = save_active;
|
|
||||||
NextActiveWindowID = save_next;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CMD_EvalCommandsLineByLine() {
|
|
||||||
BSet set = GetBSet(PrimaryWindowID);
|
|
||||||
EvalCommandsLineByLine(set);
|
|
||||||
} RegisterCommand(CMD_EvalCommandsLineByLine, "", "Goes line by line over a buffer and evaluates every line as a command, ignores empty or lines starting with '//'");
|
|
||||||
|
|
||||||
BufferID LoadConfig(String config_path) {
|
|
||||||
ReportConsolef("Loading config %S...", config_path);
|
|
||||||
Window *window = GetWindow(NullWindowID);
|
|
||||||
View *view = WindowOpenBufferView(window, config_path);
|
|
||||||
Buffer *buffer = GetBuffer(view->active_buffer);
|
|
||||||
buffer->special = true;
|
|
||||||
EvalCommandsLineByLine({window, view, buffer});
|
|
||||||
if (window->active_view == view->id) {
|
|
||||||
window->active_view = NullViewID;
|
|
||||||
}
|
|
||||||
return buffer->id;
|
|
||||||
}
|
|
||||||
@@ -1,45 +1,50 @@
|
|||||||
typedef void CoroutineProc(mco_coro *co);
|
typedef void CoroutineFunction(mco_coro *co);
|
||||||
CoData *CoCurr = NULL;
|
CCtx *_CoroutineContext = NULL;
|
||||||
|
|
||||||
void CoDestroy(CoData *n) {
|
CCtx *GetCoroutineContext() {
|
||||||
|
Assert(_CoroutineContext);
|
||||||
|
return _CoroutineContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyCoroutine(CCtx *n) {
|
||||||
mco_destroy(n->co);
|
mco_destroy(n->co);
|
||||||
Release(&n->arena);
|
Release(&n->arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoRemove(String name) {
|
void RemoveCoroutine(String name) {
|
||||||
IterRemove(ActiveCoroutines) {
|
IterRemove(ActiveCoroutines) {
|
||||||
IterRemovePrepare(ActiveCoroutines);
|
IterRemovePrepare(ActiveCoroutines);
|
||||||
if (it.name == name) {
|
if (it.name == name) {
|
||||||
mco_destroy(it.co);
|
DestroyCoroutine(&it);
|
||||||
Release(&it.arena);
|
|
||||||
remove_item = true;
|
remove_item = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CoAdd(x) CoAddEx(x, #x)
|
CCtx CreateCoroutine(CoroutineFunction *func, String name) {
|
||||||
CoData *CoAddEx(CoroutineProc *proc, String name) {
|
mco_desc desc = mco_desc_init(func, 0);
|
||||||
mco_desc desc = mco_desc_init(proc, 0);
|
|
||||||
mco_coro *coro = NULL;
|
mco_coro *coro = NULL;
|
||||||
mco_result ok = mco_create(&coro, &desc);
|
mco_result ok = mco_create(&coro, &desc);
|
||||||
if (ok != MCO_SUCCESS) {
|
assert(ok == MCO_SUCCESS);
|
||||||
ReportWarningf("failed to create coroutine %d", ok);
|
return {coro, name};
|
||||||
return NULL;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Add(&ActiveCoroutines, {coro, name});
|
#define AddCoroutine(x) AddCoroutineEx(x, #x)
|
||||||
|
CCtx *AddCoroutineEx(CoroutineFunction *func, String name) {
|
||||||
|
CCtx coroutine = CreateCoroutine(func, name);
|
||||||
|
Add(&ActiveCoroutines, coroutine);
|
||||||
return GetLast(ActiveCoroutines);
|
return GetLast(ActiveCoroutines);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoResume(CoData *dat) {
|
void ResumeCoroutine(CCtx *dat) {
|
||||||
CoData *prev_curr = CoCurr;
|
CCtx *prev_curr = _CoroutineContext;
|
||||||
CoCurr = dat;
|
_CoroutineContext = dat;
|
||||||
mco_result ok = mco_resume(dat->co);
|
mco_result ok = mco_resume(dat->co);
|
||||||
Assert(ok == MCO_SUCCESS);
|
Assert(ok == MCO_SUCCESS);
|
||||||
CoCurr = prev_curr;
|
_CoroutineContext = prev_curr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoUpdate(Event *event) {
|
void UpdateCoroutines(Event *event) {
|
||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
double start = GetTimeSeconds();
|
double start = GetTimeSeconds();
|
||||||
for (;ActiveCoroutines.len;) {
|
for (;ActiveCoroutines.len;) {
|
||||||
@@ -49,20 +54,31 @@ void CoUpdate(Event *event) {
|
|||||||
|
|
||||||
mco_state status = mco_status(it.co);
|
mco_state status = mco_status(it.co);
|
||||||
if (status == MCO_DEAD) {
|
if (status == MCO_DEAD) {
|
||||||
CoDestroy(&it);
|
DestroyCoroutine(&it);
|
||||||
remove_item = true;
|
remove_item = true;
|
||||||
} else {
|
} else {
|
||||||
mco_push(it.co, &event, sizeof(Event *));
|
mco_push(it.co, &event, sizeof(Event *));
|
||||||
CoCurr = ⁢
|
_CoroutineContext = ⁢
|
||||||
mco_result ok = mco_resume(it.co);
|
mco_result ok = mco_resume(it.co);
|
||||||
if (ok != MCO_SUCCESS) {
|
if (ok != MCO_SUCCESS) {
|
||||||
ReportWarningf("failed to resume coroutine %d", ok);
|
ReportErrorf("failed to resume coroutine %d", ok);
|
||||||
CoDestroy(&it);
|
DestroyCoroutine(&it);
|
||||||
remove_item = true;
|
remove_item = true;
|
||||||
}
|
}
|
||||||
|
_CoroutineContext = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if SLOW_BUILD
|
||||||
|
//:CoroutineLeakCheck
|
||||||
|
// Make sure we don't leak scratch arenas between coroutine boundaries.
|
||||||
|
// it leads to memory corruption! If you hit Assert here make sure you
|
||||||
|
// don't leak the scratch arena.
|
||||||
|
for (int i = 0; i < Lengthof(ScratchArenas); i += 1) {
|
||||||
|
Assert(ScratchArenas[i].refs == 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
double took = GetTimeSeconds() - start;
|
double took = GetTimeSeconds() - start;
|
||||||
if (took > (0.016666 / 3.0)) {
|
if (took > (0.016666 / 3.0)) {
|
||||||
break;
|
break;
|
||||||
@@ -70,7 +86,7 @@ void CoUpdate(Event *event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Event *CoYield(mco_coro *co) {
|
Event *Yield(mco_coro *co) {
|
||||||
mco_result ok = mco_yield(co);
|
mco_result ok = mco_yield(co);
|
||||||
Assert(ok == MCO_SUCCESS);
|
Assert(ok == MCO_SUCCESS);
|
||||||
|
|
||||||
|
|||||||
219
src/text_editor/data_desc.cpp
Normal file
219
src/text_editor/data_desc.cpp
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
typedef U32 TokenKind;
|
||||||
|
enum {
|
||||||
|
TokenKind_EOF = 0,
|
||||||
|
TokenKind_Ident = (1<<0),
|
||||||
|
TokenKind_String = (1<<1),
|
||||||
|
TokenKind_Numeric = (1<<2),
|
||||||
|
TokenKind_Comment = (1<<3),
|
||||||
|
TokenKind_Error = (1<<4),
|
||||||
|
|
||||||
|
TokenKind_OpenBrace = (1<<5),
|
||||||
|
TokenKind_CloseBrace = (1<<6),
|
||||||
|
TokenKind_Colon = (1<<7),
|
||||||
|
TokenKind_Comma = (1<<8),
|
||||||
|
TokenKind_Minus = (1<<9),
|
||||||
|
TokenKind_Tag = (1<<10),
|
||||||
|
TokenKind_Or = (1<<11),
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef U32 TokenGroup;
|
||||||
|
enum {
|
||||||
|
TokenGroup_String = (TokenKind_Numeric|TokenKind_String|TokenKind_Ident),
|
||||||
|
TokenGroup_Symbol = (TokenKind_OpenBrace|TokenKind_CloseBrace|TokenKind_Colon|TokenKind_Comma|TokenKind_Minus|TokenKind_Tag|TokenKind_Or),
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Token {
|
||||||
|
TokenKind kind;
|
||||||
|
union {
|
||||||
|
struct {char *data; Int len;};
|
||||||
|
String string;
|
||||||
|
};
|
||||||
|
Float f;
|
||||||
|
Int line, column;
|
||||||
|
char *file;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Lexer {
|
||||||
|
Allocator allocator;
|
||||||
|
char *at;
|
||||||
|
char *start;
|
||||||
|
char *end;
|
||||||
|
char *file;
|
||||||
|
Int line, column;
|
||||||
|
};
|
||||||
|
|
||||||
|
Lexer MakeLexer(Allocator allocator, String string, char *file, Int line, Int column) {
|
||||||
|
Lexer lexer = {allocator, string.data, string.data, string.data + string.len, file, line, column};
|
||||||
|
return lexer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Advance(Lexer *lex) {
|
||||||
|
if (lex->at < lex->end) {
|
||||||
|
if (lex->at[0] == '\n') {
|
||||||
|
lex->line += 1;
|
||||||
|
lex->column = 0;
|
||||||
|
}
|
||||||
|
lex->column += 1;
|
||||||
|
lex->at += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Advance(Lexer *lex, int n) {
|
||||||
|
for (int i = 0; i < n; i += 1) Advance(lex);
|
||||||
|
}
|
||||||
|
|
||||||
|
String AsString(Lexer *lex) {
|
||||||
|
String result = {lex->at, lex->end - lex->at};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char At(Lexer *lex) {
|
||||||
|
if (lex->at < lex->end) {
|
||||||
|
return lex->at[0];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EatWhitespace(Lexer *lex) {
|
||||||
|
while (IsWhitespace(At(lex))) {
|
||||||
|
Advance(lex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LexString(Lexer *lex, Token *t) {
|
||||||
|
t->kind = TokenKind_String;
|
||||||
|
char end_char = t->data[0];
|
||||||
|
t->data += 1;
|
||||||
|
for (;;) {
|
||||||
|
char c = At(lex);
|
||||||
|
if (c == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c == end_char) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c == '\\') {
|
||||||
|
Advance(lex);
|
||||||
|
}
|
||||||
|
Advance(lex);
|
||||||
|
}
|
||||||
|
Advance(lex);
|
||||||
|
t->len = (Int)(lex->at - t->data) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LexDigit(Lexer *lex, Token *t) {
|
||||||
|
t->kind = TokenKind_Numeric;
|
||||||
|
for (;;) {
|
||||||
|
char c = At(lex);
|
||||||
|
if (c == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!IsDigit(c)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Advance(lex);
|
||||||
|
}
|
||||||
|
t->len = (Int)(lex->at - t->data);
|
||||||
|
t->f = (Float)strtoll(t->data, NULL, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsOkForIdent(Lexer *lex, char c) {
|
||||||
|
bool result = IsAlphanumeric(c) || c == '_' || c == '/';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Token Next(Lexer *lex) {
|
||||||
|
EatWhitespace(lex);
|
||||||
|
Token t = {};
|
||||||
|
t.data = lex->at;
|
||||||
|
t.len = 1;
|
||||||
|
t.line = lex->line;
|
||||||
|
t.column = lex->column;
|
||||||
|
t.file = lex->file;
|
||||||
|
char c = At(lex);
|
||||||
|
Advance(lex);
|
||||||
|
|
||||||
|
if (c == 0) {
|
||||||
|
return t;
|
||||||
|
} else if (c == '{') {
|
||||||
|
t.kind = TokenKind_OpenBrace;
|
||||||
|
} else if (c == '}') {
|
||||||
|
t.kind = TokenKind_CloseBrace;
|
||||||
|
} else if (c == ':') {
|
||||||
|
t.kind = TokenKind_Colon;
|
||||||
|
} else if (c == ',') {
|
||||||
|
t.kind = TokenKind_Comma;
|
||||||
|
} else if (IsDigit(c)) {
|
||||||
|
LexDigit(lex, &t);
|
||||||
|
} else if (c == '"') {
|
||||||
|
LexString(lex, &t);
|
||||||
|
} else if (c == '`') {
|
||||||
|
LexString(lex, &t);
|
||||||
|
} else if (IsOkForIdent(lex, c)) {
|
||||||
|
t.kind = TokenKind_String;
|
||||||
|
for (;;) {
|
||||||
|
char cc = At(lex);
|
||||||
|
bool ok = IsOkForIdent(lex, cc);
|
||||||
|
if (!ok) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Advance(lex);
|
||||||
|
}
|
||||||
|
t.len = (Int)(lex->at - t.data);
|
||||||
|
} else {
|
||||||
|
t.kind = TokenKind_Error;
|
||||||
|
t.string = Format(lex->allocator, "Got invalid character when parsing: '%c'", c);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestDataDesc() {
|
||||||
|
Scratch scratch;
|
||||||
|
String s = "{/usr/bin/python3, `Hidden`, Input:Clipboard}";
|
||||||
|
Lexer lexer = MakeLexer(scratch, s, "test", 0, 0);
|
||||||
|
{
|
||||||
|
Token tok = {};
|
||||||
|
tok = Next(&lexer);
|
||||||
|
Assert(tok.kind == TokenKind_OpenBrace);
|
||||||
|
Assert(tok.len == 1);
|
||||||
|
Assert(tok.data[0] == '{');
|
||||||
|
|
||||||
|
tok = Next(&lexer);
|
||||||
|
Assert(tok.kind == TokenKind_String);
|
||||||
|
Assert(tok.string == "/usr/bin/python3");
|
||||||
|
|
||||||
|
tok = Next(&lexer);
|
||||||
|
Assert(tok.kind == TokenKind_Comma);
|
||||||
|
Assert(tok.string == ",");
|
||||||
|
|
||||||
|
tok = Next(&lexer);
|
||||||
|
Assert(tok.kind == TokenKind_String);
|
||||||
|
Assert(tok.string == "Hidden");
|
||||||
|
|
||||||
|
tok = Next(&lexer);
|
||||||
|
Assert(tok.kind == TokenKind_Comma);
|
||||||
|
Assert(tok.string == ",");
|
||||||
|
|
||||||
|
tok = Next(&lexer);
|
||||||
|
Assert(tok.kind == TokenKind_String);
|
||||||
|
Assert(tok.string == "Input");
|
||||||
|
|
||||||
|
tok = Next(&lexer);
|
||||||
|
Assert(tok.kind == TokenKind_Colon);
|
||||||
|
Assert(tok.string == ":");
|
||||||
|
|
||||||
|
tok = Next(&lexer);
|
||||||
|
Assert(tok.kind == TokenKind_String);
|
||||||
|
Assert(tok.string == "Clipboard");
|
||||||
|
|
||||||
|
tok = Next(&lexer);
|
||||||
|
Assert(tok.kind == TokenKind_CloseBrace);
|
||||||
|
Assert(tok.string == "}");
|
||||||
|
|
||||||
|
tok = Next(&lexer);
|
||||||
|
Assert(tok.kind == TokenKind_EOF);
|
||||||
|
}
|
||||||
|
|
||||||
|
} RegisterFunction(&TestFunctions, TestDataDesc);
|
||||||
@@ -10,6 +10,13 @@ Rect2I GetVisibleCells(Window *window) {
|
|||||||
|
|
||||||
Int _cx = size.x / window->font->char_spacing;
|
Int _cx = size.x / window->font->char_spacing;
|
||||||
Int _cy = size.y / window->font->line_spacing;
|
Int _cy = size.y / window->font->line_spacing;
|
||||||
|
|
||||||
|
// This function is mostly used for rendering, these magic numbers are here
|
||||||
|
// to make sure we are not showing unrendered part of the screen to the user.
|
||||||
|
// These were derived by testing and experience. We are only rendering the
|
||||||
|
// part that is on screen but need to account for the arbitrary pixel scrolling.
|
||||||
|
// That is a cell can be half on screen, it's not like vim where you are stuck
|
||||||
|
// to the grid.
|
||||||
Int cx = _cx + 1;
|
Int cx = _cx + 1;
|
||||||
Int cy = _cy + 2;
|
Int cy = _cy + 2;
|
||||||
|
|
||||||
@@ -58,7 +65,7 @@ void DrawVisibleText(Window *window, Color tint) {
|
|||||||
Rect2 rect = Rect2FromSize(p + g->offset, g->size);
|
Rect2 rect = Rect2FromSize(p + g->offset, g->size);
|
||||||
|
|
||||||
if (codepoint != '\n' && codepoint != '\r' && codepoint != ' ' && codepoint != '\t') {
|
if (codepoint != '\n' && codepoint != '\r' && codepoint != ' ' && codepoint != '\t') {
|
||||||
PushQuad2D(RenderArena, &Vertices, rect, g->atlas_bounding_box, tint);
|
PushQuad2D(RenderArena, &Vertices, Round(rect), g->atlas_bounding_box, tint);
|
||||||
}
|
}
|
||||||
|
|
||||||
text_offset_x += window->font->char_spacing;
|
text_offset_x += window->font->char_spacing;
|
||||||
@@ -96,6 +103,7 @@ void DrawUnderline(Window *window, View *view, Buffer *buffer, Range range, Colo
|
|||||||
Vec2I max = {xy_max.col * window->font->char_spacing, (xy_max.line + 1) * window->font->line_spacing};
|
Vec2I max = {xy_max.col * window->font->char_spacing, (xy_max.line + 1) * window->font->line_spacing};
|
||||||
Rect2I rect = {min, max};
|
Rect2I rect = {min, max};
|
||||||
Rect2I scrolled_rect = rect - view->scroll + window->document_rect.min;
|
Rect2I scrolled_rect = rect - view->scroll + window->document_rect.min;
|
||||||
|
if (scrolled_rect.min.x < window->document_rect.min.x) scrolled_rect.min.x = window->document_rect.min.x;
|
||||||
DrawRectOutline(scrolled_rect, color);
|
DrawRectOutline(scrolled_rect, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,21 +208,22 @@ void DrawWindow(Window *window, Event &event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Underline word under mouse cursor
|
// Underline word under mouse cursor
|
||||||
Caret caret = view->carets[0];
|
if (1) {
|
||||||
Vec2I mouse = MouseVec2I();
|
Caret caret = view->carets[0];
|
||||||
bool mouse_in_document = AreOverlapping(mouse, window->document_rect);
|
Vec2I mouse = MouseVec2I();
|
||||||
if (mouse_in_document) {
|
bool mouse_in_document = AreOverlapping(mouse, window->document_rect);
|
||||||
View *view = GetView(window->active_view);
|
if (mouse_in_document) {
|
||||||
Buffer *buffer = GetBuffer(view->active_buffer);
|
Int p = ScreenSpaceToBufferPosErrorOutOfBounds(window, view, buffer, mouse);
|
||||||
Int p = ScreenSpaceToBufferPosErrorOutOfBounds(window, view, buffer, mouse);
|
if (p != -1) {
|
||||||
if (p != -1) {
|
Range range = EncloseLoadWord(buffer, p);
|
||||||
Range range = EncloseLoadWord(buffer, p);
|
if (InBounds(caret.range, p)) range = caret.range;
|
||||||
if (InBounds(caret.range, p)) range = caret.range;
|
DrawUnderline(window, view, buffer, range, MouseUnderlineColor, 2);
|
||||||
DrawUnderline(window, view, buffer, range, MouseUnderlineColor, 2);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Ctrl()) {
|
if (event.ctrl) {
|
||||||
|
Caret caret = view->carets[0];
|
||||||
if (is_active) {
|
if (is_active) {
|
||||||
if (GetSize(caret.range) == 0) {
|
if (GetSize(caret.range) == 0) {
|
||||||
Range range = EncloseLoadWord(buffer, caret.range.min);
|
Range range = EncloseLoadWord(buffer, caret.range.min);
|
||||||
@@ -263,10 +272,12 @@ void DrawWindow(Window *window, Event &event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if PLUGIN_SEARCH_WINDOW
|
||||||
if (SearchWindowID == window->id) {
|
if (SearchWindowID == window->id) {
|
||||||
SetScissor(window->line_numbers_rect);
|
SetScissor(window->line_numbers_rect);
|
||||||
DrawString(window->font, u"Find: ", ToVec2(window->line_numbers_rect.min), color_text_line_numbers);
|
DrawString(window->font, u"Find: ", ToVec2(window->line_numbers_rect.min), color_text_line_numbers);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Draw scrollbar
|
// Draw scrollbar
|
||||||
if (DrawScrollbar && window->draw_scrollbar) {
|
if (DrawScrollbar && window->draw_scrollbar) {
|
||||||
|
|||||||
@@ -347,11 +347,18 @@ struct { String string; SDL_Keycode value; } SDLKeycodeConversionTable[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void FillEventWithBasicData(Event *event) {
|
void FillEventWithBasicData(Event *event) {
|
||||||
SDL_Keymod mod = SDL_GetModState();
|
#if OS_WINDOWS
|
||||||
event->shift = (mod & SDL_KMOD_SHIFT) != 0;
|
if (GetKeyState(VK_SHIFT) & 0x8000) event->shift = 1;
|
||||||
event->ctrl = (mod & SDL_KMOD_CTRL) != 0;
|
if (GetKeyState(VK_CONTROL) & 0x8000) event->ctrl = 1;
|
||||||
event->alt = (mod & SDL_KMOD_ALT) != 0;
|
if (GetKeyState(VK_MENU) & 0x8000) event->alt = 1;
|
||||||
event->super = (mod & SDL_KMOD_GUI) != 0;
|
if (GetKeyState(VK_LWIN) & 0x8000) event->super = 1;
|
||||||
|
#else
|
||||||
|
SDL_Keymod mod = SDL_GetModState();
|
||||||
|
event->shift = (mod & SDL_KMOD_SHIFT) != 0;
|
||||||
|
event->ctrl = (mod & SDL_KMOD_CTRL) != 0;
|
||||||
|
event->alt = (mod & SDL_KMOD_ALT) != 0;
|
||||||
|
event->super = (mod & SDL_KMOD_GUI) != 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
float xmouse, ymouse;
|
float xmouse, ymouse;
|
||||||
SDL_GetMouseState(&xmouse, &ymouse);
|
SDL_GetMouseState(&xmouse, &ymouse);
|
||||||
@@ -371,6 +378,7 @@ Event TranslateSDLEvent(SDL_Event *input_event) {
|
|||||||
FillEventWithBasicData(&event);
|
FillEventWithBasicData(&event);
|
||||||
|
|
||||||
switch (input_event->type) {
|
switch (input_event->type) {
|
||||||
|
|
||||||
case SDL_EVENT_QUIT: {
|
case SDL_EVENT_QUIT: {
|
||||||
event.kind = EVENT_QUIT;
|
event.kind = EVENT_QUIT;
|
||||||
} break;
|
} break;
|
||||||
@@ -472,109 +480,34 @@ inline void AddEvent(Array<Event> *events, Event ev) {
|
|||||||
Add(events, ev);
|
Add(events, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
Array<Event> GetEventsForFrame(Allocator allocator) {
|
void GetEventsForFrame(Array<Event> *events) {
|
||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
Array<Event> result = {allocator};
|
For (EventPlayback) {
|
||||||
if (EventPlayback.len) {
|
Add(events, it);
|
||||||
result = TightCopy(allocator, EventPlayback);
|
|
||||||
EventPlayback.len = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
if (WaitForEventsState) {
|
if (WaitForEventsState) {
|
||||||
SDL_WaitEvent(&event);
|
SDL_WaitEvent(&event);
|
||||||
Event ev = TranslateSDLEvent(&event);
|
Event ev = TranslateSDLEvent(&event);
|
||||||
AddEvent(&result, ev);
|
AddEvent(events, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
Event ev = TranslateSDLEvent(&event);
|
Event ev = TranslateSDLEvent(&event);
|
||||||
AddEvent(&result, ev);
|
AddEvent(events, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.len == 0) {
|
if (events->len == 0) {
|
||||||
Event event = {};
|
Event ev = {};
|
||||||
FillEventWithBasicData(&event);
|
FillEventWithBasicData(&ev);
|
||||||
event.kind = EVENT_UPDATE;
|
ev.kind = EVENT_UPDATE;
|
||||||
Add(&result, event);
|
Add(events, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert(result.len);
|
Assert(events->len);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Serializer {
|
|
||||||
Buffer *buffer; // for writing
|
|
||||||
};
|
|
||||||
|
|
||||||
void Serialize(Serializer *s, String name, EventKind *kind) {
|
|
||||||
RawAppendf(s->buffer, "ev.%S = %s; ", name, EventKindStrings[*kind]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serialize(Serializer *s, String name, Int *datum) {
|
|
||||||
if (*datum == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (name == "key") {
|
|
||||||
RawAppendf(s->buffer, "ev.%S = %s; ", name, SDLKeycodeToName((SDL_Keycode)*datum));
|
|
||||||
} else {
|
|
||||||
RawAppendf(s->buffer, "ev.%S = %lld; ", name, (long long)*datum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serialize(Serializer *s, String name, uint32_t *datum) {
|
|
||||||
Int d = *datum;
|
|
||||||
Serialize(s, name, &d);
|
|
||||||
*datum = (uint32_t)d;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serialize(Serializer *s, String name, uint8_t *datum) {
|
|
||||||
Int d = *datum;
|
|
||||||
Serialize(s, name, &d);
|
|
||||||
*datum = (uint8_t)d;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serialize(Serializer *s, String name, int16_t *datum) {
|
|
||||||
Int d = *datum;
|
|
||||||
Serialize(s, name, &d);
|
|
||||||
*datum = (int16_t)d;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serialize(Serializer *s, String name, float *datum) {
|
|
||||||
if (*datum == 0.f) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RawAppendf(s->buffer, "ev.%S = %f; ", name, *datum);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serialize(Serializer *s, String name, char **text) {
|
|
||||||
String str = *text;
|
|
||||||
if (str.len == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RawAppendf(s->buffer, "ev.%S = \"%S\"; ", name, str);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SerializeBegin(Serializer *s) {
|
|
||||||
RawAppendf(s->buffer, "{Event ev = {};");
|
|
||||||
}
|
|
||||||
|
|
||||||
void SerializeEnd(Serializer *s) {
|
|
||||||
RawAppendf(s->buffer, "Add(&EventPlayback, ev);}\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// :Event
|
|
||||||
void Serialize(Serializer *s, Event *e) {
|
|
||||||
SerializeBegin(s);
|
|
||||||
#define X(TYPE, KIND, NAME) Serialize(s, #NAME, &e->NAME);
|
|
||||||
EVENT_FIELDS
|
|
||||||
#undef X
|
|
||||||
SerializeEnd(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define Ctrl() event.ctrl
|
|
||||||
#define Alt() event.alt
|
|
||||||
#define Shift() event.shift
|
|
||||||
|
|
||||||
#define Press(KEY) (event.key == KEY)
|
#define Press(KEY) (event.key == KEY)
|
||||||
#define CtrlPress(KEY) (event.key == KEY && event.ctrl)
|
#define CtrlPress(KEY) (event.key == KEY && event.ctrl)
|
||||||
@@ -583,10 +516,8 @@ void Serialize(Serializer *s, Event *e) {
|
|||||||
#define CtrlShiftPress(KEY) (event.key == KEY && event.ctrl && event.shift)
|
#define CtrlShiftPress(KEY) (event.key == KEY && event.ctrl && event.shift)
|
||||||
#define CtrlAltPress(KEY) (event.key == KEY && event.ctrl && event.alt)
|
#define CtrlAltPress(KEY) (event.key == KEY && event.ctrl && event.alt)
|
||||||
#define AltShiftPress(KEY) (event.key == KEY && event.shift && event.alt)
|
#define AltShiftPress(KEY) (event.key == KEY && event.shift && event.alt)
|
||||||
#define MouseVec2() \
|
#define MouseVec2() Vec2 { (float)event.xmouse, (float)event.ymouse }
|
||||||
Vec2 { (float)event.xmouse, (float)event.ymouse }
|
#define MouseVec2I() Vec2I { (Int) event.xmouse, (Int)event.ymouse }
|
||||||
#define MouseVec2I() \
|
|
||||||
Vec2I { (Int) event.xmouse, (Int)event.ymouse }
|
|
||||||
#define Mouse(x) (event.kind == EVENT_MOUSE_##x)
|
#define Mouse(x) (event.kind == EVENT_MOUSE_##x)
|
||||||
#define MousePress() (Mouse(LEFT) || Mouse(RIGHT) || Mouse(MIDDLE))
|
#define MousePress() (Mouse(LEFT) || Mouse(RIGHT) || Mouse(MIDDLE))
|
||||||
#define MouseUp() (Mouse(LEFT_UP) || Mouse(RIGHT_UP) || Mouse(MIDDLE_UP))
|
#define MouseUp() (Mouse(LEFT_UP) || Mouse(RIGHT_UP) || Mouse(MIDDLE_UP))
|
||||||
|
|||||||
160
src/text_editor/fuzzy_search_view.cpp
Normal file
160
src/text_editor/fuzzy_search_view.cpp
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
// float NewFuzzyRate(String16 s, String16 p) {
|
||||||
|
// float score = 0;
|
||||||
|
// // try to do this: https://github.com/junegunn/fzf/blob/master/src/algo/algo.go
|
||||||
|
// return score;
|
||||||
|
// }
|
||||||
|
|
||||||
|
float FuzzyRate(String16 s, String16 p) {
|
||||||
|
float score = 0;
|
||||||
|
for (Int outer_pi = 0; outer_pi < p.len; outer_pi += 1) {
|
||||||
|
String16 pit = Skip(p, outer_pi);
|
||||||
|
if (IsWhitespace(At(pit, 0))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float matching = 0;
|
||||||
|
for (Int outer_si = 0; outer_si < s.len; outer_si += 1) {
|
||||||
|
String16 sit = Skip(s, outer_si);
|
||||||
|
if (IsWhitespace(At(sit, 0))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Int si = 0;
|
||||||
|
Int pi = 0;
|
||||||
|
for (;si < sit.len && pi < pit.len;) {
|
||||||
|
while (si < sit.len && IsWhitespace(sit[si])) si += 1;
|
||||||
|
while (pi < pit.len && IsWhitespace(pit[pi])) pi += 1;
|
||||||
|
if (pi >= pit.len) break;
|
||||||
|
if (si >= sit.len) break;
|
||||||
|
|
||||||
|
if (ToLowerCase(sit[si]) == ToLowerCase(pit[pi])) {
|
||||||
|
matching += 1.0f;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
si += 1;
|
||||||
|
pi += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
score += matching;
|
||||||
|
}
|
||||||
|
score = score / (float)s.len;
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool MergeSortCompare(FuzzyPair *a, FuzzyPair *b) {
|
||||||
|
bool result = a->rating > b->rating;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array<FuzzyPair> FuzzySearchLines(Allocator allocator, Buffer *buffer, Int line_min, Int line_max, String16 needle) {
|
||||||
|
ProfileFunction();
|
||||||
|
if (line_min < 0 || line_min >= buffer->line_starts.len) return {};
|
||||||
|
if (line_max < 0 || line_min > buffer->line_starts.len) return {};
|
||||||
|
Array<FuzzyPair> ratings = {allocator};
|
||||||
|
Reserve(&ratings, line_max - line_min + 4);
|
||||||
|
for (Int i = line_min; i < line_max; i += 1) {
|
||||||
|
String16 s = GetLineStringWithoutNL(buffer, i);
|
||||||
|
|
||||||
|
Int idx = 0;
|
||||||
|
if (Seek(s, u"||>", &idx, SeekFlag_None)) {
|
||||||
|
s = GetPrefix(s, idx);
|
||||||
|
} else if (Seek(s, u"<||", &idx, SeekFlag_None)) {
|
||||||
|
s = GetPrefix(s, idx);
|
||||||
|
}
|
||||||
|
s = Trim(s);
|
||||||
|
|
||||||
|
float rating = FuzzyRate(s, needle);
|
||||||
|
Add(&ratings, {(int32_t)i, rating});
|
||||||
|
}
|
||||||
|
Array<FuzzyPair> temp = Copy(allocator, ratings);
|
||||||
|
MergeSort(ratings.len, ratings.data, temp.data);
|
||||||
|
return ratings;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateFuzzySearchView() {
|
||||||
|
ProfileFunction();
|
||||||
|
Scratch scratch;
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
String16 line_string = GetLineStringWithoutNL(active.buffer, 0);
|
||||||
|
uint64_t hash = HashBytes(line_string.data, line_string.len * sizeof(char16_t));
|
||||||
|
if (active.view->prev_search_line_hash != hash) {
|
||||||
|
active.view->prev_search_line_hash = hash;
|
||||||
|
Array<FuzzyPair> ratings = FuzzySearchLines(scratch, active.buffer, 1, active.buffer->line_starts.len, line_string);
|
||||||
|
|
||||||
|
Buffer *scratch_buff = CreateScratchBuffer(scratch, active.buffer->cap);
|
||||||
|
RawAppend(scratch_buff, line_string);
|
||||||
|
For (IterateInReverse(&ratings)) {
|
||||||
|
String16 s = GetLineStringWithoutNL(active.buffer, it.index);
|
||||||
|
if (s.len == 0) continue;
|
||||||
|
RawAppend(scratch_buff, u"\n");
|
||||||
|
RawAppend(scratch_buff, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
Caret caret = active.view->carets[0];
|
||||||
|
SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets);
|
||||||
|
SelectEntireBuffer(active.view);
|
||||||
|
Replace(active.view, GetString(scratch_buff));
|
||||||
|
active.view->carets[0] = caret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String16 FetchFuzzyViewLoadLine(View *view) {
|
||||||
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
|
Range range = view->carets[0].range;
|
||||||
|
String16 string = GetString(buffer, range);
|
||||||
|
Int line = PosToLine(buffer, range.min);
|
||||||
|
if (GetSize(range) == 0 || line == 0) {
|
||||||
|
if (line == 0) {
|
||||||
|
line = ClampTop(1ll, buffer->line_starts.len - 1ll);
|
||||||
|
}
|
||||||
|
string = GetLineStringWithoutNL(buffer, line);
|
||||||
|
|
||||||
|
Int idx = 0;
|
||||||
|
String16 delim = u"||>";
|
||||||
|
if (Seek(string, delim, &idx, SeekFlag_None)) {
|
||||||
|
string = Skip(string, idx + delim.len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMD_OpenLoadWord() {
|
||||||
|
Scratch scratch;
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
For (active.view->carets) {
|
||||||
|
Range range = it.range;
|
||||||
|
if (GetSize(range) == 0) {
|
||||||
|
range = EncloseLoadWord(active.buffer, GetFront(it));
|
||||||
|
}
|
||||||
|
String16 load_word = GetString(active.buffer, range);
|
||||||
|
Open(load_word);
|
||||||
|
}
|
||||||
|
} RegisterCommand(CMD_OpenLoadWord, "ctrl-q | f12", "Open a link under the caret (file link, url, command) or open the selection");
|
||||||
|
|
||||||
|
void CMD_OpenForFuzzyView() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
if (active.view->carets.len == 1 && GetSize(active.view->carets[0].range) == 0) {
|
||||||
|
String16 string = FetchFuzzyViewLoadLine(active.view);
|
||||||
|
Open(string);
|
||||||
|
} else {
|
||||||
|
CMD_OpenLoadWord();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMD_OpenAndSetGotoNext() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
String16 string = FetchFuzzyViewLoadLine(active.view);
|
||||||
|
main.window->active_goto_list = active.view->id;
|
||||||
|
main.window->goto_list_pos = active.view->carets[0].range.min;
|
||||||
|
Open(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFuzzy(View *view) {
|
||||||
|
AddCommand(&view->commands, "Open", OpenKeySet, CMD_OpenForFuzzyView);
|
||||||
|
view->update_hook = UpdateFuzzySearchView;
|
||||||
|
}
|
||||||
@@ -8,11 +8,17 @@ bool WaitForEventsState = true;
|
|||||||
bool RunGCThisFrame;
|
bool RunGCThisFrame;
|
||||||
bool SearchCaseSensitive = false;
|
bool SearchCaseSensitive = false;
|
||||||
bool SearchWordBoundary = false;
|
bool SearchWordBoundary = false;
|
||||||
#if OS_WINDOWS
|
|
||||||
bool BreakOnError = true;
|
|
||||||
#else
|
|
||||||
bool BreakOnError = false;
|
bool BreakOnError = false;
|
||||||
#endif
|
Int ErrorCount;
|
||||||
|
|
||||||
|
Allocator SysAllocator = {SystemAllocatorProc};
|
||||||
|
float DPIScale = 1.0f;
|
||||||
|
|
||||||
|
// @WARNING: be careful about using this, should only be used for debugging
|
||||||
|
// the problem with this is that we want events to be reproducible.
|
||||||
|
// We eat as many events as we can in a frame, we abstract the frame and so on.
|
||||||
|
// Dont use it
|
||||||
|
Int FrameID;
|
||||||
|
|
||||||
WindowID WindowIDs;
|
WindowID WindowIDs;
|
||||||
ViewID ViewIDs;
|
ViewID ViewIDs;
|
||||||
@@ -22,25 +28,26 @@ Array<Window *> Windows;
|
|||||||
Array<View *> Views;
|
Array<View *> Views;
|
||||||
Array<Buffer *> Buffers;
|
Array<Buffer *> Buffers;
|
||||||
|
|
||||||
// First window
|
View *LogView;
|
||||||
|
Buffer *LogBuffer;
|
||||||
|
|
||||||
|
// Replace with ref to null buffer?
|
||||||
BufferID NullBufferID;
|
BufferID NullBufferID;
|
||||||
ViewID NullViewID;
|
ViewID NullViewID;
|
||||||
WindowID NullWindowID;
|
WindowID NullWindowID;
|
||||||
|
|
||||||
// hidden floating window
|
#if PLUGIN_SEARCH_WINDOW
|
||||||
WindowID DebugWindowID;
|
WindowID SearchWindowID;
|
||||||
ViewID DebugViewID;
|
ViewID SearchViewID;
|
||||||
BufferID DebugBufferID;
|
BufferID SearchBufferID;
|
||||||
WindowID CommandWindowID;
|
#endif
|
||||||
WindowID StatusBarWindowID;
|
|
||||||
WindowID SearchWindowID;
|
Trigger *EscapeKey;
|
||||||
ViewID SearchViewID;
|
Trigger *EnterKey;
|
||||||
BufferID SearchBufferID;
|
Trigger *OpenKeySet;
|
||||||
WindowID BuildWindowID;
|
Trigger *EnterOrEscapeKeySet;
|
||||||
ViewID BuildViewID;
|
Trigger *AltEnterKeySet;
|
||||||
BufferID BuildBufferID;
|
Trigger *ShiftEnterKeySet;
|
||||||
BufferID SearchProjectBufferID;
|
|
||||||
BufferID GlobalConfigBufferID;
|
|
||||||
|
|
||||||
WindowID NextActiveWindowID;
|
WindowID NextActiveWindowID;
|
||||||
WindowID ActiveWindowID;
|
WindowID ActiveWindowID;
|
||||||
@@ -52,11 +59,6 @@ WindowID ResizerHover = {-1};
|
|||||||
Caret DocumentAnchor;
|
Caret DocumentAnchor;
|
||||||
Vec2I MouseMiddleAnchor;
|
Vec2I MouseMiddleAnchor;
|
||||||
|
|
||||||
Buffer *GCInfoBuffer;
|
|
||||||
Buffer *EventBuffer;
|
|
||||||
Buffer *TraceBuffer;
|
|
||||||
View *TraceView;
|
|
||||||
|
|
||||||
RandomSeed UniqueBufferNameSeed = {};
|
RandomSeed UniqueBufferNameSeed = {};
|
||||||
Array<Event> EventPlayback;
|
Array<Event> EventPlayback;
|
||||||
BlockArena Perm;
|
BlockArena Perm;
|
||||||
@@ -86,16 +88,21 @@ String Intern(InternTable *table, String string) {
|
|||||||
// optimize worst offenders (like event text)
|
// optimize worst offenders (like event text)
|
||||||
InternTable GlobalInternTable;
|
InternTable GlobalInternTable;
|
||||||
|
|
||||||
Function *LastExecutedManualCommand;
|
Array<Command> GlobalCommands;
|
||||||
Array<CommandData> CommandFunctions;
|
|
||||||
Array<FunctionData> TestFunctions;
|
Array<FunctionData> TestFunctions;
|
||||||
Array<Variable> Variables;
|
Array<Variable> Variables;
|
||||||
|
|
||||||
Array<Process> ActiveProcesses = {};
|
Array<Process> ActiveProcesses = {};
|
||||||
Array<String> ProcessEnviroment = {};
|
Array<String> ProcessEnviroment = {};
|
||||||
|
|
||||||
struct CoData { mco_coro *co; String name; BlockArena arena; bool dont_wait_until_resolved; void *user_ctx; };
|
struct CCtx {
|
||||||
Array<CoData> ActiveCoroutines;
|
mco_coro *co;
|
||||||
|
String name;
|
||||||
|
BlockArena arena;
|
||||||
|
bool dont_wait_until_resolved;
|
||||||
|
void *user_ctx;
|
||||||
|
};
|
||||||
|
Array<CCtx> ActiveCoroutines;
|
||||||
|
|
||||||
Color GruvboxDark0Hard = {0x1d, 0x20, 0x21, 0xff};
|
Color GruvboxDark0Hard = {0x1d, 0x20, 0x21, 0xff};
|
||||||
Color GruvboxDark0 = {0x28, 0x28, 0x28, 0xff};
|
Color GruvboxDark0 = {0x28, 0x28, 0x28, 0xff};
|
||||||
@@ -159,14 +166,35 @@ RegisterVariable(Int, WaitForEvents, 1);
|
|||||||
RegisterVariable(Int, DrawLineNumbers, 1);
|
RegisterVariable(Int, DrawLineNumbers, 1);
|
||||||
RegisterVariable(Int, DrawScrollbar, 1);
|
RegisterVariable(Int, DrawScrollbar, 1);
|
||||||
RegisterVariable(Int, IndentSize, 4);
|
RegisterVariable(Int, IndentSize, 4);
|
||||||
RegisterVariable(String, IndentKindWhichIsTabsOrSpaces, "spaces");
|
|
||||||
RegisterVariable(Int, FontSize, 15);
|
RegisterVariable(Int, FontSize, 15);
|
||||||
RegisterVariable(String, WorkDir, "");
|
|
||||||
RegisterVariable(String, PathToFont, "");
|
RegisterVariable(String, PathToFont, "");
|
||||||
RegisterVariable(String, WindowsVCVarsPathToLoadDevEnviroment, "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvars64.bat");
|
|
||||||
RegisterVariable(Float, UndoMergeTime, 0.3);
|
RegisterVariable(Float, UndoMergeTime, 0.3);
|
||||||
RegisterVariable(Float, JumpHistoryMergeTime, 0.3);
|
RegisterVariable(Float, JumpHistoryMergeTime, 0.3);
|
||||||
RegisterVariable(String, InternetBrowser, "firefox");
|
RegisterVariable(String, InternetBrowser, "firefox");
|
||||||
RegisterVariable(String, OpenCodePatterns, ".c .h .cpp .hpp .cc .cxx .rs .go .zig .py .lua .js .ts .jsx .tsx .java .kt .swift .cs .rb .php .html .css .scss .bat .sh .bash .zsh .sql .asm .s .cmake .make .json .yaml .toml .ini .txt .md .rst .Makefile .Dockerfile .gitignore .bashrc .zshrc");
|
RegisterVariable(String, OpenCodePatterns, ".c .h .cpp .hpp .cc .cxx .rs .go .zig .py .lua .js .ts .jsx .tsx .java .kt .swift .cs .rb .php .html .css .scss .bat .sh .bash .zsh .sql .asm .s .cmake .make .json .yaml .toml .ini .txt .md .rst .Makefile .Dockerfile .gitignore .bashrc .zshrc");
|
||||||
RegisterVariable(String, OpenCodeExcludePatterns, "");
|
RegisterVariable(String, OpenCodeExcludePatterns, "");
|
||||||
RegisterVariable(Int, TrimTrailingWhitespace, 1);
|
RegisterVariable(Int, TrimTrailingWhitespace, 1);
|
||||||
|
RegisterVariable(String, HomeFolder, "");
|
||||||
|
RegisterVariable(String, ConfigFolder, "");
|
||||||
|
|
||||||
|
// PROJECT_MANAGEMENT
|
||||||
|
// Set at the beginning of the program to current directory
|
||||||
|
RegisterVariable(String, ProjectFolder, "");
|
||||||
|
|
||||||
|
// PLUGIN_BUILD_WINDOW
|
||||||
|
RegisterVariable(String, Build1OnWindows, "build.bat slow");
|
||||||
|
RegisterVariable(String, Build1OnUnix, "sh build.sh slow");
|
||||||
|
RegisterVariable(String, Build2OnWindows, "build.bat");
|
||||||
|
RegisterVariable(String, Build2OnUnix, "sh build.sh");
|
||||||
|
RegisterVariable(String, Build3OnWindows, "build.bat release");
|
||||||
|
RegisterVariable(String, Build3OnUnix, "sh build.sh release");
|
||||||
|
RegisterVariable(String, Build4OnWindows, "build.bat release");
|
||||||
|
RegisterVariable(String, Build4OnUnix, "sh build.sh release");
|
||||||
|
|
||||||
|
|
||||||
|
// PLUGIN_LOAD_VCVARS
|
||||||
|
RegisterVariable(String, VCVarsPath, "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvars64.bat");
|
||||||
|
|
||||||
|
// PLUGIN_REMEDYBG
|
||||||
|
RegisterVariable(String, BinaryUnderDebug, "");
|
||||||
|
RegisterVariable(String, RemedyBGPath, "remedybg.exe");
|
||||||
@@ -17,7 +17,8 @@ Alternatively you can open with your keyboard - ctrl-q / f12, try that also!
|
|||||||
Navigate to next parts of the guide by executing them like these commands you just played
|
Navigate to next parts of the guide by executing them like these commands you just played
|
||||||
with:
|
with:
|
||||||
|
|
||||||
guide_big_picture.txt
|
:GuideBigPicture
|
||||||
guide_project.txt
|
:GuideNavigation
|
||||||
guide_bindings_and_config.txt
|
:GuideProjects
|
||||||
|
:GuideBindings
|
||||||
|
:GuideConfig
|
||||||
|
|||||||
249
src/text_editor/plugin_basic_commands.cpp
Normal file
249
src/text_editor/plugin_basic_commands.cpp
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
void CMD_Redo() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
RedoEdit(active.buffer, &active.view->carets);
|
||||||
|
} RegisterCommand(CMD_Redo, "ctrl-shift-z", "Redo after undoing changes to the buffer");
|
||||||
|
|
||||||
|
void CMD_Undo() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
UndoEdit(active.buffer, &active.view->carets);
|
||||||
|
} RegisterCommand(CMD_Undo, "ctrl-z", "Undo last change you made to the buffer");
|
||||||
|
|
||||||
|
void CMD_EncloseLine() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
EncloseLine(active.view);
|
||||||
|
} RegisterCommand(CMD_EncloseLine, "ctrl-l", "Select the entire line on which your caret is placed");
|
||||||
|
|
||||||
|
void CMD_SelectAll() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
SelectEntireBuffer(active.view);
|
||||||
|
active.view->update_scroll = false;
|
||||||
|
} RegisterCommand(CMD_SelectAll, "ctrl-a", "Select the entire buffer");
|
||||||
|
|
||||||
|
void CMD_KillSelectedLines() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
KillSelectedLines(active.view);
|
||||||
|
} RegisterCommand(CMD_KillSelectedLines, "ctrl-shift-k", "Delete the selected lines, don't put to clipboard");
|
||||||
|
|
||||||
|
void CMD_IndentSelectedLines() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
IndentSelectedLines(active.view);
|
||||||
|
} RegisterCommand(CMD_IndentSelectedLines, "ctrl-rightbracket | tab", "");
|
||||||
|
|
||||||
|
void CMD_DedentSelectedLines() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
IndentSelectedLines(active.view, true);
|
||||||
|
} RegisterCommand(CMD_DedentSelectedLines, "ctrl-leftbracket | shift-tab", "");
|
||||||
|
|
||||||
|
void CMD_DuplicateLineDown() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
DuplicateLine(active.view, DIR_DOWN);
|
||||||
|
} RegisterCommand(CMD_DuplicateLineDown, "ctrl-alt-down", "");
|
||||||
|
|
||||||
|
void CMD_CreateCursorDown() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
CreateCursorVertical(active.view, DIR_DOWN);
|
||||||
|
} RegisterCommand(CMD_CreateCursorDown, "alt-shift-down", "");
|
||||||
|
|
||||||
|
void CMD_SelectDownToEmptyLine() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_DOWN, CTRL_PRESSED, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_SelectDownToEmptyLine, "ctrl-shift-down", "");
|
||||||
|
|
||||||
|
void CMD_MoveLineDown() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCaretsLine(active.view, DIR_DOWN);
|
||||||
|
} RegisterCommand(CMD_MoveLineDown, "alt-down", "");
|
||||||
|
|
||||||
|
void CMD_MoveDownToEmptyLine() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_DOWN, CTRL_PRESSED);
|
||||||
|
} RegisterCommand(CMD_MoveDownToEmptyLine, "ctrl-down", "");
|
||||||
|
|
||||||
|
void CMD_SelectDown() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_DOWN, false, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_SelectDown, "shift-down", "");
|
||||||
|
|
||||||
|
void CMD_MoveDown() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_DOWN);
|
||||||
|
} RegisterCommand(CMD_MoveDown, "down", "");
|
||||||
|
|
||||||
|
void CMD_DuplicateLineUp() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
DuplicateLine(active.view, DIR_UP);
|
||||||
|
} RegisterCommand(CMD_DuplicateLineUp, "ctrl-alt-up", "");
|
||||||
|
|
||||||
|
void CMD_CreateCursorUp() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
CreateCursorVertical(active.view, DIR_UP);
|
||||||
|
} RegisterCommand(CMD_CreateCursorUp, "alt-shift-up", "");
|
||||||
|
|
||||||
|
void CMD_SelectUpToEmptyLine() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_UP, CTRL_PRESSED, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_SelectUpToEmptyLine, "ctrl-shift-up", "");
|
||||||
|
|
||||||
|
void CMD_MoveLineUp() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCaretsLine(active.view, DIR_UP);
|
||||||
|
} RegisterCommand(CMD_MoveLineUp, "alt-up", "");
|
||||||
|
|
||||||
|
void CMD_MoveUpToEmptyLine() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_UP, CTRL_PRESSED);
|
||||||
|
} RegisterCommand(CMD_MoveUpToEmptyLine, "ctrl-up", "");
|
||||||
|
|
||||||
|
void CMD_SelectUp() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_UP, false, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_SelectUp, "shift-up", "");
|
||||||
|
|
||||||
|
void CMD_MoveUp() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_UP);
|
||||||
|
} RegisterCommand(CMD_MoveUp, "up", "");
|
||||||
|
|
||||||
|
void CMD_BoundarySelectLeft() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_LEFT, CTRL_PRESSED, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_BoundarySelectLeft, "ctrl-shift-left", "");
|
||||||
|
|
||||||
|
void CMD_BoundaryMoveLeft() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_LEFT, CTRL_PRESSED);
|
||||||
|
} RegisterCommand(CMD_BoundaryMoveLeft, "ctrl-left", "");
|
||||||
|
|
||||||
|
void CMD_SelectLeft() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_LEFT, false, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_SelectLeft, "shift-left", "");
|
||||||
|
|
||||||
|
void CMD_MoveLeft() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_LEFT);
|
||||||
|
} RegisterCommand(CMD_MoveLeft, "left", "");
|
||||||
|
|
||||||
|
void CMD_BoundarySelectRight() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_RIGHT, CTRL_PRESSED, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_BoundarySelectRight, "ctrl-shift-right", "");
|
||||||
|
|
||||||
|
void CMD_BoundaryMoveRight() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_RIGHT, CTRL_PRESSED);
|
||||||
|
} RegisterCommand(CMD_BoundaryMoveRight, "ctrl-right", "");
|
||||||
|
|
||||||
|
void CMD_SelectRight() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_RIGHT, false, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_SelectRight, "shift-right", "");
|
||||||
|
|
||||||
|
void CMD_MoveRight() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCarets(active.view, DIR_RIGHT);
|
||||||
|
} RegisterCommand(CMD_MoveRight, "right", "");
|
||||||
|
|
||||||
|
void CMD_MoveUpAPage() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCursorByPageSize(active.window, DIR_UP);
|
||||||
|
} RegisterCommand(CMD_MoveUpAPage, "pageup", "");
|
||||||
|
|
||||||
|
void CMD_SelectUpPage() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCursorByPageSize(active.window, DIR_UP, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_SelectUpPage, "shift-pageup", "");
|
||||||
|
|
||||||
|
void CMD_MoveToStart() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
SelectRange(active.view, MakeRange(0));
|
||||||
|
} RegisterCommand(CMD_MoveToStart, "ctrl-pageup", "");
|
||||||
|
|
||||||
|
void CMD_SelectDownPage() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCursorByPageSize(active.window, DIR_DOWN, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_SelectDownPage, "shift-pagedown", "");
|
||||||
|
|
||||||
|
void CMD_MoveToEnd() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
SelectRange(active.view, MakeRange(active.buffer->len));
|
||||||
|
} RegisterCommand(CMD_MoveToEnd, "ctrl-pagedown", "");
|
||||||
|
|
||||||
|
void CMD_MoveDownPage() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCursorByPageSize(active.window, DIR_DOWN);
|
||||||
|
} RegisterCommand(CMD_MoveDownPage, "pagedown", "");
|
||||||
|
|
||||||
|
void CMD_SelectToLineStart() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCursorToSide(active.view, DIR_LEFT, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_SelectToLineStart, "shift-home", "");
|
||||||
|
|
||||||
|
void CMD_MoveToLineStart() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCursorToSide(active.view, DIR_LEFT);
|
||||||
|
} RegisterCommand(CMD_MoveToLineStart, "home", "");
|
||||||
|
|
||||||
|
void CMD_MoveToLineEnd() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCursorToSide(active.view, DIR_RIGHT);
|
||||||
|
} RegisterCommand(CMD_MoveToLineEnd, "end", "");
|
||||||
|
|
||||||
|
void CMD_SelectToLineEnd() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
MoveCursorToSide(active.view, DIR_RIGHT, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_SelectToLineEnd, "shift-end", "");
|
||||||
|
|
||||||
|
void CMD_DeleteCharacter() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
Delete(active.view, DIR_LEFT);
|
||||||
|
} RegisterCommand(CMD_DeleteCharacter, "shift-backspace | backspace", "");
|
||||||
|
|
||||||
|
void CMD_DeleteBoundary() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
Delete(active.view, DIR_LEFT, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_DeleteBoundary, "ctrl-backspace", "");
|
||||||
|
|
||||||
|
void CMD_DeleteForward() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
Delete(active.view, DIR_RIGHT);
|
||||||
|
} RegisterCommand(CMD_DeleteForward, "shift-delete | delete", "");
|
||||||
|
|
||||||
|
void CMD_DeleteForwardBoundary() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
Delete(active.view, DIR_RIGHT, SHIFT_PRESS);
|
||||||
|
} RegisterCommand(CMD_DeleteForwardBoundary, "ctrl-delete", "");
|
||||||
|
|
||||||
|
void CMD_InsertNewLineUp() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets);
|
||||||
|
MoveCursorToSide(active.view, DIR_LEFT);
|
||||||
|
IndentedNewLine(active.view);
|
||||||
|
MoveCarets(active.view, DIR_UP);
|
||||||
|
} RegisterCommand(CMD_InsertNewLineUp, "ctrl-shift-enter", "");
|
||||||
|
|
||||||
|
void CMD_InsertNewLineDown() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets);
|
||||||
|
MoveCursorToSide(active.view, DIR_RIGHT);
|
||||||
|
IndentedNewLine(active.view);
|
||||||
|
} RegisterCommand(CMD_InsertNewLineDown, "ctrl-enter", "");
|
||||||
|
|
||||||
|
void CMD_NewLine() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
IndentedNewLine(active.view);
|
||||||
|
} RegisterCommand(CMD_NewLine, "enter | shift-enter", "");
|
||||||
|
|
||||||
|
void CMD_CreateCaretOnNextFind() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
String16 string = GetString(active.buffer, active.view->carets[0].range);
|
||||||
|
Caret caret = FindNext(active.buffer, string, active.view->carets[0]);
|
||||||
|
Insert(&active.view->carets, caret, 0);
|
||||||
|
MergeCarets(active.buffer, &active.view->carets);
|
||||||
|
} RegisterCommand(CMD_CreateCaretOnNextFind, "ctrl-d", "");
|
||||||
|
|
||||||
|
void CMD_ClearCarets() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
active.view->carets.len = 1;
|
||||||
|
active.view->carets[0] = MakeCaret(GetFront(active.view->carets[0]));
|
||||||
|
} RegisterCommand(CMD_ClearCarets, "escape", "Clear all carets and reset to 1 caret, also do some windowing stuff that closes things on escape");
|
||||||
95
src/text_editor/plugin_build_window.cpp
Normal file
95
src/text_editor/plugin_build_window.cpp
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
WindowID BuildWindowID;
|
||||||
|
ViewID BuildViewID;
|
||||||
|
BufferID BuildBufferID;
|
||||||
|
|
||||||
|
void InitBuildWindow() {
|
||||||
|
Window *window = CreateWind();
|
||||||
|
BuildWindowID = window->id;
|
||||||
|
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectFolder, "build"));
|
||||||
|
buffer->special = true;
|
||||||
|
buffer->no_history = true;
|
||||||
|
BuildBufferID = buffer->id;
|
||||||
|
View *view = CreateView(buffer->id);
|
||||||
|
view->special = true;
|
||||||
|
BuildViewID = view->id;
|
||||||
|
window->active_view = view->id;
|
||||||
|
window->secondary_window_style = true;
|
||||||
|
window->draw_line_highlight = true;
|
||||||
|
window->primary = false;
|
||||||
|
window->visible = false;
|
||||||
|
window->lose_visibility_on_escape = true;
|
||||||
|
window->jump_history = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutBuildWindow(Rect2I *rect, int16_t wx, int16_t wy) {
|
||||||
|
Unused(wx); Unused(wy);
|
||||||
|
Window *window = GetWindow(BuildWindowID);
|
||||||
|
Rect2I copy_rect = *rect;
|
||||||
|
if (!window->visible) {
|
||||||
|
rect = ©_rect;
|
||||||
|
}
|
||||||
|
Int barsize = window->font->line_spacing * 10;
|
||||||
|
window->document_rect = window->total_rect = CutBottom(rect, barsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
BSet ExecBuild(String windows_cmd, String unix_cmd, String working_dir = ProjectFolder) {
|
||||||
|
SaveAll();
|
||||||
|
Scratch scratch;
|
||||||
|
BSet build = GetBSet(BuildWindowID);
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
SelectRange(build.view, Range{});
|
||||||
|
ResetBuffer(build.buffer);
|
||||||
|
{
|
||||||
|
ExecArgs args = ShellArgs(scratch, build.view->id, windows_cmd, working_dir);
|
||||||
|
args.scroll_to_end = true;
|
||||||
|
if (!OS_WINDOWS) {
|
||||||
|
args.cmd = unix_cmd;
|
||||||
|
}
|
||||||
|
Exec(args);
|
||||||
|
}
|
||||||
|
main.window->active_goto_list = build.view->id;
|
||||||
|
main.window->goto_list_pos = 0;
|
||||||
|
build.window->visible = true;
|
||||||
|
return build;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMD_Build1() {
|
||||||
|
ExecBuild(Build1OnWindows, Build1OnUnix);
|
||||||
|
} RegisterCommand(CMD_Build1, "ctrl-b", "Run Build1OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer");
|
||||||
|
|
||||||
|
void CMD_Build2() {
|
||||||
|
ExecBuild(Build2OnWindows, Build2OnUnix);
|
||||||
|
} RegisterCommand(CMD_Build2, "alt-b", "Run Build2OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer");
|
||||||
|
|
||||||
|
void CMD_Build3() {
|
||||||
|
ExecBuild(Build3OnWindows, Build3OnUnix);
|
||||||
|
} RegisterCommand(CMD_Build3, "shift-b", "Run Build3OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer");
|
||||||
|
|
||||||
|
void CMD_Build4() {
|
||||||
|
ExecBuild(Build4OnWindows, Build4OnUnix);
|
||||||
|
} RegisterCommand(CMD_Build4, "ctrl-alt-b", "Run Build4OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer");
|
||||||
|
|
||||||
|
void CMD_RunFile() {
|
||||||
|
Scratch scratch;
|
||||||
|
BSet primary = GetBSet(PrimaryWindowID);
|
||||||
|
// @todo: this calls for language_cpp, language_c, language_python etc.
|
||||||
|
String windows_command = "";
|
||||||
|
String unix_command = "";
|
||||||
|
if (EndsWith(primary.buffer->name, ".py")) {
|
||||||
|
unix_command = windows_command = Format(scratch, "python %S", primary.buffer->name);
|
||||||
|
} else {
|
||||||
|
windows_command = Format(scratch, "cl %S -Fe:cfile.exe /Zi /FC /nologo /WX /W3 /wd4200 /wd4334 /diagnostics:column && cfile.exe && del cfile.*", primary.buffer->name);
|
||||||
|
unix_command = Format(scratch, "bash <<EOF\nclang %S -o cfile.exe -g -Wall -Wno-missing-braces -Wno-writable-strings -Wno-writable-strings -fsanitize=address -fdiagnostics-absolute-paths -lm \n./cfile.exe \nrm cfile.exe\nEOF", primary.buffer->name);
|
||||||
|
}
|
||||||
|
ExecBuild(windows_command, unix_command, GetDirectory(primary.buffer));
|
||||||
|
} RegisterCommand(CMD_RunFile, "", "Run and build current file, currently only C/C++ supported");
|
||||||
|
|
||||||
|
void CMD_ShowBuildWindow() {
|
||||||
|
BSet main = GetBSet(BuildWindowID);
|
||||||
|
if (ActiveWindowID != BuildWindowID) {
|
||||||
|
main.window->visible = true;
|
||||||
|
NextActiveWindowID = BuildWindowID;
|
||||||
|
} else {
|
||||||
|
main.window->visible = false;
|
||||||
|
}
|
||||||
|
} RegisterCommand(CMD_ShowBuildWindow, "ctrl-grave", "Toggles visibility of the build window");
|
||||||
86
src/text_editor/plugin_command_window.cpp
Normal file
86
src/text_editor/plugin_command_window.cpp
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
WindowID CommandWindowID;
|
||||||
|
|
||||||
|
void CMD_ShowCommands() {
|
||||||
|
BSet command_bar = GetBSet(CommandWindowID);
|
||||||
|
command_bar.window->visible = true;
|
||||||
|
NextActiveWindowID = command_bar.window->id;
|
||||||
|
ResetBuffer(command_bar.buffer);
|
||||||
|
For (GlobalCommands) {
|
||||||
|
RawAppendf(command_bar.buffer, "\n:%-30S <|| ", it.name);
|
||||||
|
if (it.docs.len) {
|
||||||
|
RawAppendf(command_bar.buffer, "%S", it.docs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command_bar.view->update_scroll = true;
|
||||||
|
SelectRange(command_bar.view, Range{});
|
||||||
|
} RegisterCommand(CMD_ShowCommands, "ctrl-shift-p", "List available commands and their documentation inside the command window");
|
||||||
|
|
||||||
|
void CMD_ShowDebugBufferList() {
|
||||||
|
BSet command_bar = GetBSet(CommandWindowID);
|
||||||
|
command_bar.window->visible = true;
|
||||||
|
NextActiveWindowID = command_bar.window->id;
|
||||||
|
ResetBuffer(command_bar.buffer);
|
||||||
|
For (Buffers) {
|
||||||
|
bool is_special = it->special || it->temp || it->dont_try_to_save_in_bulk_ops;
|
||||||
|
#if PLUGIN_DIRECTORY_NAVIGATION
|
||||||
|
is_special |= it->is_dir;
|
||||||
|
#endif
|
||||||
|
if (!is_special) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RawAppendf(command_bar.buffer, "\n%S", it->name);
|
||||||
|
}
|
||||||
|
command_bar.view->update_scroll = true;
|
||||||
|
SelectRange(command_bar.view, Range{});
|
||||||
|
} RegisterCommand(CMD_ShowDebugBufferList, "ctrl-shift-alt-p", "Show full list of buffers, including the special ones that normally just clutter list");
|
||||||
|
|
||||||
|
void CMD_ShowBufferList() {
|
||||||
|
BSet command_bar = GetBSet(CommandWindowID);
|
||||||
|
command_bar.window->visible = true;
|
||||||
|
NextActiveWindowID = command_bar.window->id;
|
||||||
|
ResetBuffer(command_bar.buffer);
|
||||||
|
For (Buffers) {
|
||||||
|
bool is_special = it->special || it->temp || it->dont_try_to_save_in_bulk_ops;
|
||||||
|
#if PLUGIN_DIRECTORY_NAVIGATION
|
||||||
|
is_special |= it->is_dir;
|
||||||
|
#endif
|
||||||
|
if (is_special) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RawAppendf(command_bar.buffer, "\n%S", it->name);
|
||||||
|
}
|
||||||
|
command_bar.view->update_scroll = true;
|
||||||
|
SelectRange(command_bar.view, Range{});
|
||||||
|
} RegisterCommand(CMD_ShowBufferList, "ctrl-p", "List open buffers inside the command window that you can fuzzy search over");
|
||||||
|
|
||||||
|
void LayoutCommandWindow(Rect2I *rect, int16_t wx, int16_t wy) {
|
||||||
|
Unused(wy);
|
||||||
|
Window *window = GetWindow(CommandWindowID);
|
||||||
|
Rect2I copy_rect = *rect;
|
||||||
|
if (!window->visible) {
|
||||||
|
rect = ©_rect;
|
||||||
|
}
|
||||||
|
Int barsize = Clamp((Int)window->font->line_spacing*10, (Int)0, (Int)wx - 100);
|
||||||
|
window->document_rect = window->total_rect = CutBottom(rect, barsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitCommandWindow() {
|
||||||
|
Window *window = CreateWind();
|
||||||
|
CommandWindowID = window->id;
|
||||||
|
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectFolder, "command_bar"));
|
||||||
|
buffer->special = true;
|
||||||
|
buffer->no_history = true;
|
||||||
|
View *view = CreateView(buffer->id);
|
||||||
|
view->special = true;
|
||||||
|
SetFuzzy(view);
|
||||||
|
window->active_view = view->id;
|
||||||
|
window->draw_line_numbers = false;
|
||||||
|
window->draw_scrollbar = false;
|
||||||
|
window->secondary_window_style = true;
|
||||||
|
window->draw_line_highlight = true;
|
||||||
|
window->primary = false;
|
||||||
|
window->visible = false;
|
||||||
|
window->sync_visibility_with_focus = true;
|
||||||
|
window->lose_focus_on_escape = true;
|
||||||
|
window->jump_history = false;
|
||||||
|
}
|
||||||
156
src/text_editor/plugin_config.cpp
Normal file
156
src/text_editor/plugin_config.cpp
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#if PLUGIN_CONFIG
|
||||||
|
BufferID GlobalConfigBufferID;
|
||||||
|
|
||||||
|
void CMD_OpenConfig() {
|
||||||
|
Buffer *buffer = GetBuffer(GlobalConfigBufferID);
|
||||||
|
Open(buffer->name);
|
||||||
|
} RegisterCommand(CMD_OpenConfig, "", "Open the global config file");
|
||||||
|
|
||||||
|
void CMD_OpenConfigOptions() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
JumpTempBuffer(&main);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
For (Variables) {
|
||||||
|
RawAppendf(main.buffer, "\n:Set %-50S ", it.name);
|
||||||
|
switch(it.type) {
|
||||||
|
case VariableType_Color: RawAppendf(main.buffer, "%x", it.color->value); break;
|
||||||
|
case VariableType_String: RawAppendf(main.buffer, "'%S'", *it.string); break;
|
||||||
|
case VariableType_Int: RawAppendf(main.buffer, "%lld", (long long)*it.i); break;
|
||||||
|
case VariableType_Float: RawAppendf(main.buffer, "%f", *it.f); break;
|
||||||
|
default: InvalidCodepath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
For (GlobalCommands) {
|
||||||
|
RawAppendf(main.buffer, "\n:Set %-50S '%S'", it.name, it.binding);
|
||||||
|
}
|
||||||
|
} RegisterCommand(CMD_OpenConfigOptions, "", "List available variables and associated documentation inside the command window");
|
||||||
|
|
||||||
|
void EvalCommandsLineByLine(BSet set) {
|
||||||
|
WindowID save_last = PrimaryWindowID;
|
||||||
|
WindowID save_active = ActiveWindowID;
|
||||||
|
WindowID save_next = NextActiveWindowID;
|
||||||
|
Caret save_caret = set.view->carets[0];
|
||||||
|
ActiveWindowID = set.window->id;
|
||||||
|
PrimaryWindowID = set.window->id;
|
||||||
|
NextActiveWindowID = set.window->id;
|
||||||
|
for (Int i = 0; i < set.buffer->line_starts.len; i += 1) {
|
||||||
|
Range range = GetLineRangeWithoutNL(set.buffer, i);
|
||||||
|
String16 string = GetString(set.buffer, range);
|
||||||
|
string = Trim(string);
|
||||||
|
if (string.len == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (StartsWith(string, u"//")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Open(string);
|
||||||
|
}
|
||||||
|
set.view->carets[0] = save_caret;
|
||||||
|
PrimaryWindowID = save_last;
|
||||||
|
ActiveWindowID = save_active;
|
||||||
|
NextActiveWindowID = save_next;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMD_EvalCommandsLineByLine() {
|
||||||
|
BSet set = GetBSet(PrimaryWindowID);
|
||||||
|
EvalCommandsLineByLine(set);
|
||||||
|
} RegisterCommand(CMD_EvalCommandsLineByLine, "", "Goes line by line over a buffer and evaluates every line as a command, ignores empty or lines starting with '//'");
|
||||||
|
|
||||||
|
BufferID LoadConfig(String config_path) {
|
||||||
|
ReportConsolef("Loading config %S...", config_path);
|
||||||
|
Window *window = GetWindow(NullWindowID);
|
||||||
|
ViewID active_view_before = window->active_view;
|
||||||
|
View *view = WindowOpenBufferView(window, config_path);
|
||||||
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
|
EvalCommandsLineByLine({window, view, buffer});
|
||||||
|
if (window->active_view == view->id) {
|
||||||
|
window->active_view = active_view_before;
|
||||||
|
}
|
||||||
|
return buffer->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ExpectP(x, ...) \
|
||||||
|
if (!(x)) { \
|
||||||
|
ReportErrorf("Failed to parse command " __VA_ARGS__); \
|
||||||
|
return; \
|
||||||
|
}
|
||||||
|
|
||||||
|
void Set(String string) {
|
||||||
|
String name = SkipIdent(&string);
|
||||||
|
ExpectP(name.len != 0, "expected a variable name, instead got '%S'", string);
|
||||||
|
|
||||||
|
Variable *var = GetVariable(name);
|
||||||
|
if (var) {
|
||||||
|
SkipWhitespace(&string);
|
||||||
|
if (var->type == VariableType_String) {
|
||||||
|
char c = At(string, 0);
|
||||||
|
ExpectP(c == u'"' || c == u'\'', "Expected string to follow the command name, instead got %S", string);
|
||||||
|
string = Skip(string, 1);
|
||||||
|
String quote = SkipUntil(&string, {&c, 1});
|
||||||
|
ExpectP(At(string, 0) == c, ":Set %S <error here>, unclosed quote", name);
|
||||||
|
ReportConsolef(":Set %S %c%S%c", name, c, quote, c);
|
||||||
|
*var->string = Intern(&GlobalInternTable, quote);
|
||||||
|
} else if (var->type == VariableType_Int) {
|
||||||
|
ExpectP(IsDigit(At(string, 0)), "Expected an integer to follow the command name, instead got: %S", string);
|
||||||
|
Int number = SkipInt(&string);
|
||||||
|
ReportConsolef(":Set %S %lld", name, number);
|
||||||
|
*var->i = number;
|
||||||
|
} else if (var->type == VariableType_Float) {
|
||||||
|
ExpectP(IsDigit(At(string, 0)), "Expected float to follow the command name, instead got: %S", string);
|
||||||
|
Float number = SkipFloat(&string);
|
||||||
|
ReportConsolef(":Set %S %f", name, number);
|
||||||
|
*var->f = number;
|
||||||
|
} else if (var->type == VariableType_Color) {
|
||||||
|
ExpectP(IsHexDigit(At(string, 0)), "Expected hex integer to follow the command name, instead got: %S", string);
|
||||||
|
String begin = {string.data, 0};
|
||||||
|
while (IsHexDigit(At(string, 0))) {
|
||||||
|
string = Skip(string, 1);
|
||||||
|
begin.len += 1;
|
||||||
|
}
|
||||||
|
ReportConsolef(":Set %S %S", name, begin);
|
||||||
|
var->color->value = (uint32_t)strtoll(begin.data, NULL, 16);
|
||||||
|
} ElseInvalidCodepath();
|
||||||
|
|
||||||
|
|
||||||
|
if (name == "FontSize" || name == "PathToFont") {
|
||||||
|
ReloadFont(PathToFont, (U32)FontSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if PLUGIN_PROJECT_MANAGEMENT
|
||||||
|
if (name == "ProjectFolder") {
|
||||||
|
SetProjectFolder(*var->string);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Command *cmd = NULL;
|
||||||
|
For (GlobalCommands) {
|
||||||
|
if (it.name == name) {
|
||||||
|
cmd = ⁢
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd) {
|
||||||
|
SkipWhitespace(&string);
|
||||||
|
char c = At(string, 0);
|
||||||
|
ExpectP(c == u'"' || c == u'\'', "Expected string to follow the command name, instead got %S", string);
|
||||||
|
string = Skip(string, 1);
|
||||||
|
String quote = SkipUntil(&string, {&c, 1});
|
||||||
|
ExpectP(At(string, 0) == c, "Failed to parse command, unclose quote");
|
||||||
|
quote = Intern(&GlobalInternTable, quote);
|
||||||
|
ReportConsolef(":Set %S %c%S%c", name, c, quote, c);
|
||||||
|
Trigger *trigger = ParseKeyCached(quote);
|
||||||
|
if (trigger) {
|
||||||
|
cmd->binding = quote;
|
||||||
|
cmd->trigger = trigger;
|
||||||
|
CheckKeybindingColission();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReportErrorf("Failed to :Set, no such variable found: %S", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
4
src/text_editor/plugin_config.h
Normal file
4
src/text_editor/plugin_config.h
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#if PLUGIN_CONFIG
|
||||||
|
void Set(String string);
|
||||||
|
String InsertVariables(Allocator allocator, String string);
|
||||||
|
#endif
|
||||||
61
src/text_editor/plugin_debug_window.cpp
Normal file
61
src/text_editor/plugin_debug_window.cpp
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
WindowID DebugWindowID;
|
||||||
|
ViewID DebugViewID;
|
||||||
|
BufferID DebugBufferID;
|
||||||
|
|
||||||
|
void InitDebugWindow() {
|
||||||
|
Window *window = CreateWind();
|
||||||
|
DebugWindowID = window->id;
|
||||||
|
window->draw_line_numbers = false;
|
||||||
|
window->draw_scrollbar = false;
|
||||||
|
window->visible = false;
|
||||||
|
window->z = 2;
|
||||||
|
window->primary = false;
|
||||||
|
window->jump_history = false;
|
||||||
|
|
||||||
|
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectFolder, "debug"));
|
||||||
|
DebugBufferID = buffer->id;
|
||||||
|
buffer->no_history = true;
|
||||||
|
buffer->special = true;
|
||||||
|
|
||||||
|
View *view = CreateView(buffer->id);
|
||||||
|
view->special = true;
|
||||||
|
DebugViewID = view->id;
|
||||||
|
window->active_view = view->id;
|
||||||
|
window->visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutDebugWindow(Rect2I *rect, int16_t wx, int16_t wy) {
|
||||||
|
Unused(rect);
|
||||||
|
Window *window = GetWindow(DebugWindowID);
|
||||||
|
Rect2 screen_rect = Rect0Size((float)wx, (float)wy);
|
||||||
|
Vec2 size = GetSize(screen_rect);
|
||||||
|
|
||||||
|
Rect2 a = CutRight(&screen_rect, 0.3f * size.x);
|
||||||
|
Rect2 b = CutTop(&a, 0.4f * size.y);
|
||||||
|
Rect2 c = Shrink(b, 20);
|
||||||
|
window->document_rect = window->total_rect = ToRect2I(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateDebugWindow() {
|
||||||
|
ProfileFunction();
|
||||||
|
BSet set = GetBSet(DebugWindowID);
|
||||||
|
if (!set.window->visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
if (active.buffer->id.id == set.buffer->id.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RawReplaceText(set.buffer, GetRange(set.buffer), u"Active buffers and views:\n");
|
||||||
|
For (Views) {
|
||||||
|
Buffer *buffer = GetBuffer(it->active_buffer);
|
||||||
|
RawAppendf(set.buffer, "view->id:%lld, buffer->id:%lld, buffer->name:%S\n", (long long)it->id.id, (long long)buffer->id.id, buffer->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMD_ToggleDebug() {
|
||||||
|
Window *window = GetWindow(DebugWindowID);
|
||||||
|
window->visible = !window->visible;
|
||||||
|
} RegisterCommand(CMD_ToggleDebug, "ctrl-0", "Open a floating window that might become useful for debugging");
|
||||||
196
src/text_editor/plugin_directory_navigation.cpp
Normal file
196
src/text_editor/plugin_directory_navigation.cpp
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
// @todo: On save rename files, delete files it should apply the changes
|
||||||
|
// Instead of toying with Reopen maybe it should actually detect changes in directory etc. on update
|
||||||
|
|
||||||
|
void CMD_OpenUpFolder() {
|
||||||
|
// @todo: Test this for weird cases
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
String name = ChopLastSlash(main.buffer->name);
|
||||||
|
Open(name);
|
||||||
|
} RegisterCommand(CMD_OpenUpFolder, "ctrl-o", "Open current's file directory or up directory in other cases");
|
||||||
|
|
||||||
|
void InsertDirectoryNavigation(Buffer *buffer) {
|
||||||
|
Assert(buffer->is_dir);
|
||||||
|
Scratch scratch;
|
||||||
|
ResetBuffer(buffer);
|
||||||
|
RawAppendf(buffer, "\n");
|
||||||
|
RawAppendf(buffer, "..\n");
|
||||||
|
for (FileIter it = IterateFiles(scratch, buffer->name); IsValid(it); Advance(&it)) {
|
||||||
|
RawAppendf(buffer, "%S\n", it.filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateDirectoryNavigation() {
|
||||||
|
#if 0
|
||||||
|
ProfileFunction();
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
Buffer *buffer = active.buffer;
|
||||||
|
|
||||||
|
Scratch scratch;
|
||||||
|
Array<String> existing_lines = Split(scratch, AllocCharString(scratch, buffer), "\n");
|
||||||
|
bool modified = false;
|
||||||
|
for (FileIter it = IterateFiles(scratch, buffer->name); IsValid(it); Advance(&it)) {
|
||||||
|
bool found = false;
|
||||||
|
ForItem (existing, existing_lines) {
|
||||||
|
if (existing == it.filename) {
|
||||||
|
found = true;
|
||||||
|
UnorderedRemove(&existing_lines, existing);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found == false) {
|
||||||
|
modified = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing_lines.len > 1) {
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified) {
|
||||||
|
ReopenBuffer(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateFuzzySearchView();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenDirectoryNavigation(View *view) {
|
||||||
|
SetFuzzy(view);
|
||||||
|
// view->update_hook = UpdateDirectoryNavigation;
|
||||||
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
|
InsertDirectoryNavigation(buffer);
|
||||||
|
SelectRange(view, Range{});
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HANDLE dir;
|
||||||
|
OVERLAPPED ov;
|
||||||
|
HANDLE event;
|
||||||
|
BYTE buffer[4096];
|
||||||
|
BOOL pending;
|
||||||
|
} DirectoryWatcher;
|
||||||
|
|
||||||
|
BOOL InitDirectoryWatcher(DirectoryWatcher *w, const wchar_t *path)
|
||||||
|
{
|
||||||
|
ZeroMemory(w, sizeof(*w));
|
||||||
|
|
||||||
|
w->dir = CreateFileW(
|
||||||
|
path,
|
||||||
|
FILE_LIST_DIRECTORY,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||||
|
NULL,
|
||||||
|
OPEN_EXISTING,
|
||||||
|
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (w->dir == INVALID_HANDLE_VALUE)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
w->event = CreateEventW(NULL, TRUE, FALSE, NULL);
|
||||||
|
if (!w->event)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
w->ov.hEvent = w->event;
|
||||||
|
w->pending = FALSE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WasDirectoryModified(DirectoryWatcher *w)
|
||||||
|
{
|
||||||
|
DWORD bytes;
|
||||||
|
|
||||||
|
if (!w->pending) {
|
||||||
|
ResetEvent(w->event);
|
||||||
|
w->pending = ReadDirectoryChangesW(
|
||||||
|
w->dir,
|
||||||
|
w->buffer,
|
||||||
|
sizeof(w->buffer),
|
||||||
|
TRUE,
|
||||||
|
FILE_NOTIFY_CHANGE_FILE_NAME |
|
||||||
|
FILE_NOTIFY_CHANGE_DIR_NAME |
|
||||||
|
FILE_NOTIFY_CHANGE_SIZE |
|
||||||
|
FILE_NOTIFY_CHANGE_LAST_WRITE,
|
||||||
|
NULL,
|
||||||
|
&w->ov,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
if (!w->pending)
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WaitForSingleObject(w->event, 0) == WAIT_OBJECT_0) {
|
||||||
|
GetOverlappedResult(w->dir, &w->ov, &bytes, FALSE);
|
||||||
|
w->pending = FALSE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyDirectoryWatcher(DirectoryWatcher *w)
|
||||||
|
{
|
||||||
|
if (w->dir != INVALID_HANDLE_VALUE)
|
||||||
|
CloseHandle(w->dir);
|
||||||
|
if (w->event)
|
||||||
|
CloseHandle(w->event);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
DirectoryWatcher w;
|
||||||
|
InitDirectoryWatcher(&w, L"C:\\mydir");
|
||||||
|
|
||||||
|
if (WasDirectoryModified(&w)) {
|
||||||
|
// something changed
|
||||||
|
}
|
||||||
|
|
||||||
|
#if OS_LINUX
|
||||||
|
#include <sys/inotify.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int fd;
|
||||||
|
int wd;
|
||||||
|
} DirectoryWatcher;
|
||||||
|
|
||||||
|
int InitDirectoryWatcher(DirectoryWatcher *w, const char *path)
|
||||||
|
{
|
||||||
|
w->fd = inotify_init1(IN_NONBLOCK);
|
||||||
|
if (w->fd < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
w->wd = inotify_add_watch(
|
||||||
|
w->fd,
|
||||||
|
path,
|
||||||
|
IN_CREATE | IN_DELETE | IN_MOVE | IN_MODIFY
|
||||||
|
);
|
||||||
|
|
||||||
|
return w->wd >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WasDirectoryModified(DirectoryWatcher *w)
|
||||||
|
{
|
||||||
|
char buffer[4096];
|
||||||
|
ssize_t len = read(w->fd, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
if (len > 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0; // no data = no changes
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyDirectoryWatcher(DirectoryWatcher *w)
|
||||||
|
{
|
||||||
|
if (w->wd >= 0)
|
||||||
|
inotify_rm_watch(w->fd, w->wd);
|
||||||
|
if (w->fd >= 0)
|
||||||
|
close(w->fd);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
2
src/text_editor/plugin_directory_navigation.h
Normal file
2
src/text_editor/plugin_directory_navigation.h
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
void InsertDirectoryNavigation(struct Buffer *buffer);
|
||||||
|
void OpenDirectoryNavigation(struct View *view);
|
||||||
182
src/text_editor/plugin_file_commands.cpp
Normal file
182
src/text_editor/plugin_file_commands.cpp
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#if PLUGIN_FILE_COMMANDS
|
||||||
|
|
||||||
|
void New(Window *window, String name = "") {
|
||||||
|
View *view = GetView(window->active_view);
|
||||||
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
|
|
||||||
|
Scratch scratch;
|
||||||
|
String dir = GetDirectory(buffer);
|
||||||
|
if (name != "") {
|
||||||
|
if (!IsAbsolute(name)) {
|
||||||
|
name = Format(scratch, "%S/%S", dir, name);
|
||||||
|
}
|
||||||
|
name = GetAbsolutePath(scratch, name);
|
||||||
|
} else {
|
||||||
|
name = GetUniqueBufferName(dir, "new");
|
||||||
|
}
|
||||||
|
WindowOpenBufferView(window, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMD_New() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
New(main.window, "");
|
||||||
|
} RegisterCommand(CMD_New, "ctrl-n", "Open a new buffer with automatically generated name, use :Rename");
|
||||||
|
|
||||||
|
void CMD_SaveAll() {
|
||||||
|
SaveAll();
|
||||||
|
} RegisterCommand(CMD_SaveAll, "ctrl-shift-s", "");
|
||||||
|
|
||||||
|
void CMD_Save() {
|
||||||
|
BSet active = GetBSet(PrimaryWindowID);
|
||||||
|
SaveBuffer(active.buffer);
|
||||||
|
} RegisterCommand(CMD_Save, "ctrl-s", "Save buffer currently open in the last primary window");
|
||||||
|
|
||||||
|
void CMD_Reopen() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
ReopenBuffer(main.buffer);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
} RegisterCommand(CMD_Reopen, "", "Reads again from disk the current buffer");
|
||||||
|
|
||||||
|
void CO_Rename(mco_coro *co) {
|
||||||
|
CCtx *ctx = GetCoroutineContext();
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
Buffer *original_buffer = main.buffer;
|
||||||
|
JumpTempBuffer(&main);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
RawAppendf(main.buffer, "Rename and click enter to submit: [%S]\n :Rename :Cancel", original_buffer->name);
|
||||||
|
|
||||||
|
main.view->carets[0] = FindNext(main.buffer, u"]", MakeCaret(0));
|
||||||
|
main.view->carets[0].range.max = main.view->carets[0].range.min;
|
||||||
|
AddUIAction(main.view, "Rename", EnterKey, UIAction_Yes);
|
||||||
|
AddUIAction(main.view, "Cancel", EscapeKey, UIAction_Cancel);
|
||||||
|
UIAction action = WaitForUIAction(co, main);
|
||||||
|
if (action != UIAction_Yes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Caret a = FindNext(main.buffer, u"[", MakeCaret(-1));
|
||||||
|
Caret b = FindNext(main.buffer, u"]", MakeCaret(-1));
|
||||||
|
if (a.range.min == -1 || b.range.max == -1) {
|
||||||
|
ReportErrorf("Failed to extract the name for the Rename operation from the buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String16 string16 = GetString(main.buffer, {a.range.max + 1, b.range.max});
|
||||||
|
String string = ToString(ctx->arena, string16);
|
||||||
|
original_buffer->name = Intern(&GlobalInternTable, string);
|
||||||
|
} RegisterCoroutineCommand(CO_Rename, "", "Opens a UI asking for a new name of a buffer open in the last active primary window");
|
||||||
|
|
||||||
|
void CO_Close(mco_coro *co) {
|
||||||
|
CCtx *ctx = GetCoroutineContext();
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
if (main.buffer->special || main.buffer->temp) {
|
||||||
|
Close(main.view->id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ref = false;
|
||||||
|
For (Views) {
|
||||||
|
if (it->id == main.view->id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (it->active_buffer == main.buffer->id) {
|
||||||
|
ref = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref) {
|
||||||
|
Close(main.view->id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!main.buffer->dirty) {
|
||||||
|
Close(main.buffer->id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String question = Format(ctx->arena, "Do you want to save [%S] before closing?", main.buffer->name);
|
||||||
|
UIAction ui_action = QueryUserYesNoCancel(co, main, question);
|
||||||
|
if (ui_action == UIAction_Yes) {
|
||||||
|
SaveBuffer(main.buffer);
|
||||||
|
Close(main.buffer->id);
|
||||||
|
} else if (ui_action == UIAction_No) {
|
||||||
|
Close(main.buffer->id);
|
||||||
|
} else if (ui_action == UIAction_Cancel) {
|
||||||
|
return;
|
||||||
|
} ElseInvalidCodepath();
|
||||||
|
} RegisterCoroutineCommand(CO_Close, "ctrl-w", "Close open view in the last active primary window");
|
||||||
|
|
||||||
|
void CMD_DeleteFile() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
DeleteFile(main.buffer->name);
|
||||||
|
Close(main.buffer->id);
|
||||||
|
} RegisterCommand(CMD_DeleteFile, "", "Close the open buffer and delete it's corresponding file on disk");
|
||||||
|
|
||||||
|
void CO_CloseAll(mco_coro *co) {
|
||||||
|
ShowCloseAllUI(co);
|
||||||
|
} RegisterCoroutineCommand(CO_CloseAll, "", "Ask user which files to save and close all open normal views and buffers");
|
||||||
|
|
||||||
|
void CO_ReplaceAll(mco_coro *co) {
|
||||||
|
CCtx *ctx = GetCoroutineContext();
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
String16 string = FetchLoadWord(main.view);
|
||||||
|
String string8 = ToString(ctx->arena, string);
|
||||||
|
String16 needle = {};
|
||||||
|
String16 replace = {};
|
||||||
|
|
||||||
|
{
|
||||||
|
JumpTempBuffer(&main);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
RawAppendf(main.buffer, ":Submit (enter) :Cancel (escape)\nString to search for::%S", string8);
|
||||||
|
|
||||||
|
Caret field_seek = BaseFindNext(main.buffer, u"for::", MakeCaret(0), SeekFlag_None);
|
||||||
|
main.view->carets[0] = MakeCaret(main.buffer->len, field_seek.range.max);
|
||||||
|
AddUIAction(main.view, "Submit", EnterKey, UIAction_Yes);
|
||||||
|
AddUIAction(main.view, "Cancel", EscapeKey, UIAction_Cancel);
|
||||||
|
UIAction action = WaitForUIAction(co, main);
|
||||||
|
if (action == UIAction_Cancel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
field_seek = BaseFindNext(main.buffer, u"for::", MakeCaret(0), SeekFlag_None);
|
||||||
|
Range range = {field_seek.range.max, main.buffer->len};
|
||||||
|
needle = GetString(main.buffer, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
JumpTempBuffer(&main);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
RawAppendf(main.buffer, ":Submit (enter) :Cancel (escape)\nString to replace with::%S", ToString(ctx->arena, needle));
|
||||||
|
|
||||||
|
Caret field_seek = BaseFindNext(main.buffer, u"with::", MakeCaret(0), SeekFlag_None);
|
||||||
|
main.view->carets[0] = MakeCaret(main.buffer->len, field_seek.range.max);
|
||||||
|
AddUIAction(main.view, "Submit", EnterKey, UIAction_Yes);
|
||||||
|
AddUIAction(main.view, "Cancel", EscapeKey, UIAction_Cancel);
|
||||||
|
UIAction action = WaitForUIAction(co, main);
|
||||||
|
if (action == UIAction_Cancel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
field_seek = BaseFindNext(main.buffer, u"with::", MakeCaret(0), SeekFlag_None);
|
||||||
|
Range range = {field_seek.range.max, main.buffer->len};
|
||||||
|
replace = GetString(main.buffer, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForItem (buffer, Buffers) {
|
||||||
|
if (buffer->special || buffer->temp || buffer->dont_try_to_save_in_bulk_ops) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#if PLUGIN_DIRECTORY_NAVIGATION
|
||||||
|
if (buffer->is_dir) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
View *view = OpenBufferView(buffer->name);
|
||||||
|
if (SelectAllOccurences(view, needle)) {
|
||||||
|
Replace(view, replace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} RegisterCoroutineCommand(CO_ReplaceAll, "ctrl-shift-r", "Search and replace over the entire project, you need to select a text with format like this 'FindAnd@>ReplaceWith' and executing the command will change all occurences of FindAnd to ReplaceWith");
|
||||||
|
|
||||||
|
#endif
|
||||||
39
src/text_editor/plugin_load_vcvars.cpp
Normal file
39
src/text_editor/plugin_load_vcvars.cpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
void Windows_SetupVCVarsall(mco_coro *co) {
|
||||||
|
View *view = NULL;
|
||||||
|
{
|
||||||
|
Scratch scratch;
|
||||||
|
String working_dir = ProjectFolder;
|
||||||
|
String buffer_name = GetUniqueBufferName(working_dir, "vcvarsall-");
|
||||||
|
String cmd = Format(scratch, "\"%S\" && set", VCVarsPath);
|
||||||
|
view = OpenBufferView(buffer_name);
|
||||||
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
|
buffer->dont_try_to_save_in_bulk_ops = true;
|
||||||
|
Exec(ShellArgs(scratch, view->id, cmd, working_dir));
|
||||||
|
}
|
||||||
|
for (;;) {
|
||||||
|
if (!ProcessIsActive(view->id)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Yield(co);
|
||||||
|
}
|
||||||
|
|
||||||
|
Scratch scratch;
|
||||||
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
|
String16 string16 = GetString(buffer);
|
||||||
|
String string = ToString(scratch, string16);
|
||||||
|
Array<String> lines = Split(scratch, string, "\n");
|
||||||
|
ProcessEnviroment.len = 0;
|
||||||
|
For (lines) {
|
||||||
|
String s = Trim(it);
|
||||||
|
Array<String> values = Split(scratch, s, "=");
|
||||||
|
if (values.len == 1) continue;
|
||||||
|
Add(&ProcessEnviroment, Copy(GetSystemAllocator(), s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadVCVars() {
|
||||||
|
RemoveCoroutine("Windows_SetupVCVarsall");
|
||||||
|
CCtx *co_data = AddCoroutine(Windows_SetupVCVarsall);
|
||||||
|
co_data->dont_wait_until_resolved = true;
|
||||||
|
ResumeCoroutine(co_data);
|
||||||
|
}
|
||||||
31
src/text_editor/plugin_profiler.cpp
Normal file
31
src/text_editor/plugin_profiler.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#if PLUGIN_PROFILER
|
||||||
|
|
||||||
|
void CMD_BeginProfile() {
|
||||||
|
BeginProfiler();
|
||||||
|
} RegisterCommand(CMD_BeginProfile, "", "Start gathering profile data");
|
||||||
|
|
||||||
|
void CMD_EndProfile() {
|
||||||
|
EndProfiler();
|
||||||
|
} RegisterCommand(CMD_EndProfile, "", "Stop gathering profile data and write to disk");
|
||||||
|
|
||||||
|
SPALL_FN void BeginProfiler() {
|
||||||
|
ActivelyProfiling = true;
|
||||||
|
Scratch scratch;
|
||||||
|
String filename = Format(scratch, "te%llu.spall", (unsigned long long)GetTimeNanos());
|
||||||
|
spall_init_file(filename.data, 1, &spall_ctx);
|
||||||
|
|
||||||
|
int buffer_size = 1 * 1024 * 1024;
|
||||||
|
unsigned char *buffer = (unsigned char *)malloc(buffer_size);
|
||||||
|
spall_buffer = {buffer, (size_t)buffer_size};
|
||||||
|
|
||||||
|
spall_buffer_init(&spall_ctx, &spall_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
SPALL_FN void EndProfiler() {
|
||||||
|
ActivelyProfiling = false;
|
||||||
|
spall_buffer_quit(&spall_ctx, &spall_buffer);
|
||||||
|
free(spall_buffer.data);
|
||||||
|
spall_quit(&spall_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#if 1
|
#if PLUGIN_PROFILER
|
||||||
// SPDX-FileCopyrightText: © 2023 Phillip Trudeau-Tavara <pmttavara@protonmail.com>
|
// SPDX-FileCopyrightText: © 2023 Phillip Trudeau-Tavara <pmttavara@protonmail.com>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
@@ -21,9 +21,6 @@ TODO: Optional Helper APIs:
|
|||||||
spall_ring_flush
|
spall_ring_flush
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef SPALL_H
|
|
||||||
#define SPALL_H
|
|
||||||
|
|
||||||
#if !defined(_MSC_VER) || defined(__clang__)
|
#if !defined(_MSC_VER) || defined(__clang__)
|
||||||
#define SPALL_NOINSTRUMENT __attribute__((no_instrument_function))
|
#define SPALL_NOINSTRUMENT __attribute__((no_instrument_function))
|
||||||
#define SPALL_FORCEINLINE __attribute__((always_inline))
|
#define SPALL_FORCEINLINE __attribute__((always_inline))
|
||||||
@@ -140,9 +137,10 @@ typedef struct SpallBuffer {
|
|||||||
uint64_t first_ts;
|
uint64_t first_ts;
|
||||||
} SpallBuffer;
|
} SpallBuffer;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
static SpallProfile spall_ctx;
|
||||||
extern "C" {
|
static SpallBuffer spall_buffer;
|
||||||
#endif
|
uint64_t GetTimeNanos(void);
|
||||||
|
bool ActivelyProfiling = false;
|
||||||
|
|
||||||
SPALL_FN SPALL_FORCEINLINE bool spall__file_write(SpallProfile *ctx, const void *p, size_t n) {
|
SPALL_FN SPALL_FORCEINLINE bool spall__file_write(SpallProfile *ctx, const void *p, size_t n) {
|
||||||
if (fwrite(p, n, 1, (FILE *)ctx->data) != 1) return false;
|
if (fwrite(p, n, 1, (FILE *)ctx->data) != 1) return false;
|
||||||
@@ -296,6 +294,7 @@ SPALL_FN bool spall_flush(SpallProfile *ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SPALL_FN bool spall_buffer_init(SpallProfile *ctx, SpallBuffer *wb) {
|
SPALL_FN bool spall_buffer_init(SpallProfile *ctx, SpallBuffer *wb) {
|
||||||
|
(void)(ctx);
|
||||||
// Fails if buffer is not big enough to contain at least one event!
|
// Fails if buffer is not big enough to contain at least one event!
|
||||||
if (wb->length < sizeof(SpallBufferHeader) + sizeof(SpallBeginEventMax)) {
|
if (wb->length < sizeof(SpallBufferHeader) + sizeof(SpallBeginEventMax)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -307,6 +306,7 @@ SPALL_FN bool spall_buffer_init(SpallProfile *ctx, SpallBuffer *wb) {
|
|||||||
|
|
||||||
|
|
||||||
SPALL_FN SPALL_FORCEINLINE bool spall_buffer_begin_args(SpallProfile *ctx, SpallBuffer *wb, const char *name, int32_t name_len, const char *args, int32_t args_len, uint64_t when) {
|
SPALL_FN SPALL_FORCEINLINE bool spall_buffer_begin_args(SpallProfile *ctx, SpallBuffer *wb, const char *name, int32_t name_len, const char *args, int32_t args_len, uint64_t when) {
|
||||||
|
if (!ActivelyProfiling) return false;
|
||||||
if ((wb->head + sizeof(SpallBeginEventMax)) > wb->length) {
|
if ((wb->head + sizeof(SpallBeginEventMax)) > wb->length) {
|
||||||
if (!spall__buffer_flush(ctx, wb, when)) {
|
if (!spall__buffer_flush(ctx, wb, when)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -323,6 +323,7 @@ SPALL_FN bool spall_buffer_begin(SpallProfile *ctx, SpallBuffer *wb, const char
|
|||||||
}
|
}
|
||||||
|
|
||||||
SPALL_FN bool spall_buffer_end(SpallProfile *ctx, SpallBuffer *wb, uint64_t when) {
|
SPALL_FN bool spall_buffer_end(SpallProfile *ctx, SpallBuffer *wb, uint64_t when) {
|
||||||
|
if (!ActivelyProfiling) return false;
|
||||||
if ((wb->head + sizeof(SpallEndEvent)) > wb->length) {
|
if ((wb->head + sizeof(SpallEndEvent)) > wb->length) {
|
||||||
if (!spall__buffer_flush(ctx, wb, when)) {
|
if (!spall__buffer_flush(ctx, wb, when)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -355,59 +356,25 @@ SPALL_FN bool spall_buffer_name_process(SpallProfile *ctx, SpallBuffer *wb, cons
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#define BeginProfileScopeEx(name, len) spall_buffer_begin(&spall_ctx, &spall_buffer, (name), (len), GetTimeNanos())
|
||||||
}
|
#define BeginProfileScope(name) BeginProfileScopeEx(#name, sizeof(#name) - 1)
|
||||||
#endif
|
#define EndProfileScope() spall_buffer_end(&spall_ctx, &spall_buffer, GetTimeNanos())
|
||||||
|
|
||||||
#endif // SPALL_H
|
|
||||||
|
|
||||||
|
|
||||||
static SpallProfile spall_ctx;
|
|
||||||
static SpallBuffer spall_buffer;
|
|
||||||
uint64_t GetTimeNanos(void);
|
|
||||||
|
|
||||||
void BeginProfiler() {
|
|
||||||
spall_init_file("te.spall", 1, &spall_ctx);
|
|
||||||
|
|
||||||
int buffer_size = 1 * 1024 * 1024;
|
|
||||||
unsigned char *buffer = (unsigned char *)malloc(buffer_size);
|
|
||||||
spall_buffer = {buffer, (size_t)buffer_size};
|
|
||||||
|
|
||||||
spall_buffer_init(&spall_ctx, &spall_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EndProfiler() {
|
|
||||||
spall_buffer_quit(&spall_ctx, &spall_buffer);
|
|
||||||
free(spall_buffer.data);
|
|
||||||
spall_quit(&spall_ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _BeginProfileScope(const char *name, int len) {
|
|
||||||
spall_buffer_begin(&spall_ctx, &spall_buffer,
|
|
||||||
name, // name of your name
|
|
||||||
len, // name len minus the null terminator
|
|
||||||
GetTimeNanos() // timestamp in microseconds -- start of your timing block
|
|
||||||
);
|
|
||||||
}
|
|
||||||
#define BeginProfileScope(name) _BeginProfileScope(#name, sizeof(#name) - 1)
|
|
||||||
|
|
||||||
void EndProfileScope() {
|
|
||||||
spall_buffer_end(&spall_ctx, &spall_buffer,
|
|
||||||
GetTimeNanos() // timestamp in microseconds -- end of your timing block
|
|
||||||
);
|
|
||||||
}
|
|
||||||
#define ProfileScopeEx(name) ProfileScopeClass PROFILE_SCOPE_VAR_((name).data, (int)(name).len)
|
|
||||||
#define ProfileScope(name) ProfileScopeClass PROFILE_SCOPE_VAR_##name(#name, sizeof(#name) - 1)
|
|
||||||
#define ProfileFunction() ProfileScopeClass PROFILE_SCOPE_FUNCTION(__FUNCTION__, sizeof(__FUNCTION__) - 1)
|
|
||||||
struct ProfileScopeClass {
|
struct ProfileScopeClass {
|
||||||
ProfileScopeClass(const char *name, int len) { _BeginProfileScope(name, len); }
|
ProfileScopeClass(const char *name, int len) { BeginProfileScopeEx(name, len); }
|
||||||
~ProfileScopeClass() { EndProfileScope(); }
|
~ProfileScopeClass() { EndProfileScope(); }
|
||||||
};
|
};
|
||||||
|
#define ProfileScopeEx(name) ProfileScopeClass PROFILE_SCOPE_VAR_((name).data, (int)(name).len)
|
||||||
|
#define ProfileScope(name) ProfileScopeClass PROFILE_SCOPE_VAR_##name(#name, sizeof(#name) - 1)
|
||||||
|
#define ProfileFunction() ProfileScopeClass PROFILE_SCOPE_FUNCTION(__FUNCTION__, sizeof(__FUNCTION__) - 1)
|
||||||
|
SPALL_FN void BeginProfiler();
|
||||||
|
SPALL_FN void EndProfiler();
|
||||||
#else
|
#else
|
||||||
|
#define ProfileScopeEx(name)
|
||||||
#define ProfileScope(name)
|
#define ProfileScope(name)
|
||||||
#define ProfileFunction()
|
#define ProfileFunction()
|
||||||
#define BeginProfiler()
|
#define BeginProfiler()
|
||||||
#define EndProfiler()
|
#define EndProfiler()
|
||||||
|
#define BeginProfileScopeEx(name, len)
|
||||||
#define BeginProfileScope(name)
|
#define BeginProfileScope(name)
|
||||||
#define EndProfileScope()
|
#define EndProfileScope()
|
||||||
#endif
|
#endif
|
||||||
137
src/text_editor/plugin_project_management.cpp
Normal file
137
src/text_editor/plugin_project_management.cpp
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
void SetProjectFolder(String dir) {
|
||||||
|
ProjectFolder = Intern(&GlobalInternTable, dir);
|
||||||
|
Scratch scratch;
|
||||||
|
For (Buffers) {
|
||||||
|
if (it->special) {
|
||||||
|
String name = SkipToLastSlash(it->name);
|
||||||
|
it->name = Intern(&GlobalInternTable, Format(scratch, "%S/%S", dir, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CO_OpenCode(mco_coro *co) {
|
||||||
|
CCtx *ctx = GetCoroutineContext();
|
||||||
|
Array<String> patterns = SplitWhitespace(ctx->arena, OpenCodePatterns);
|
||||||
|
Array<String> exclude_patterns = SplitWhitespace(ctx->arena, OpenCodeExcludePatterns);
|
||||||
|
Array<String> dirs = {ctx->arena};
|
||||||
|
Add(&dirs, Copy(ctx->arena, ProjectFolder));
|
||||||
|
for (int diri = 0; diri < dirs.len; diri += 1) {
|
||||||
|
for (FileIter it = IterateFiles(ctx->arena, dirs[diri]); IsValid(it); Advance(&it)) {
|
||||||
|
bool should_open = true;
|
||||||
|
if (!it.is_directory) {
|
||||||
|
should_open = false;
|
||||||
|
ForItem (ending, patterns) {
|
||||||
|
if (EndsWith(it.absolute_path, ending)) {
|
||||||
|
should_open = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ForItem (ending, exclude_patterns) {
|
||||||
|
if (EndsWith(it.absolute_path, ending)) {
|
||||||
|
should_open = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!should_open) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (it.is_directory) {
|
||||||
|
Add(&dirs, it.absolute_path);
|
||||||
|
} else {
|
||||||
|
BufferOpenFile(it.absolute_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Yield(co);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} RegisterCoroutineCommand(
|
||||||
|
CO_OpenCode,
|
||||||
|
"",
|
||||||
|
"Open all code files in current ProjectFolder, the code files are determined through NonCodePatterns_EndsWith config variable list",
|
||||||
|
data->dont_wait_until_resolved = true;
|
||||||
|
);
|
||||||
|
|
||||||
|
void OpenProject(String directory) {
|
||||||
|
SetProjectFolder(directory);
|
||||||
|
#if PLUGIN_CONFIG
|
||||||
|
Scratch scratch;
|
||||||
|
for (FileIter it = IterateFiles(scratch, directory); IsValid(it); Advance(&it)) {
|
||||||
|
if (EndsWith(it.filename, "project.te")) {
|
||||||
|
LoadConfig(it.absolute_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMD_OpenProject() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
String directory = GetDirectory(main.buffer);
|
||||||
|
OpenProject(directory);
|
||||||
|
} RegisterCommand(CMD_OpenProject, "", "Sets the project directory, opens the project file etc.");
|
||||||
|
|
||||||
|
String16 CreateProjectBuildBat = uR"==(@echo off
|
||||||
|
mkdir build
|
||||||
|
pushd build
|
||||||
|
cl ../src/main.cpp /Zi /FC /nologo /WX /W3 /wd4200 /wd4334 /diagnostics:column
|
||||||
|
popd
|
||||||
|
)==";
|
||||||
|
|
||||||
|
String16 CreateProjectBuildSh = uR"==(#!/usr/bin/bash
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
clang ../src/main.cpp -g -Wall -Wno-missing-braces -Wno-writable-strings -fdiagnostics-absolute-paths
|
||||||
|
)==";
|
||||||
|
|
||||||
|
String16 CreateProjectMain = uR"==(#include <stdio.h>
|
||||||
|
int main() {
|
||||||
|
printf("hello world!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
)==";
|
||||||
|
|
||||||
|
void CO_CreateProject(mco_coro *co) {
|
||||||
|
String16 project_name16 = QueryUserString(co, "Please tell me, how would you like to name your project?");
|
||||||
|
String directory = GetPrimaryDirectory();
|
||||||
|
|
||||||
|
// - project_directory
|
||||||
|
// - project.te
|
||||||
|
// - build.bat
|
||||||
|
// - build.sh
|
||||||
|
// - src
|
||||||
|
// - main.c
|
||||||
|
{
|
||||||
|
Scratch scratch;
|
||||||
|
String project_name = ToString(scratch, project_name16);
|
||||||
|
String project_dir = Format(scratch, "%S/%S", directory, project_name);
|
||||||
|
String src_dir = Format(scratch, "%S/src", project_dir);
|
||||||
|
MakeDir(project_dir);
|
||||||
|
MakeDir(src_dir);
|
||||||
|
SetProjectFolder(project_dir);
|
||||||
|
|
||||||
|
Buffer *project = BufferOpenFile(Format(scratch, "%S/project.te", project_dir));
|
||||||
|
RawAppendf(project, ":OpenCode\n:Set BinaryUnderDebug '%S/build/main.exe'\n", project_dir);
|
||||||
|
SaveBuffer(project);
|
||||||
|
|
||||||
|
Buffer *build_bat = BufferOpenFile(Format(scratch, "%S/build.bat", project_dir));
|
||||||
|
RawAppend(build_bat, CreateProjectBuildBat);
|
||||||
|
SaveBuffer(build_bat);
|
||||||
|
|
||||||
|
Buffer *build_sh = BufferOpenFile(Format(scratch, "%S/build.sh", project_dir));
|
||||||
|
RawAppend(build_sh, CreateProjectBuildSh);
|
||||||
|
SaveBuffer(build_sh);
|
||||||
|
|
||||||
|
Buffer *main_file = BufferOpenFile(Format(scratch, "%S/main.cpp", src_dir));
|
||||||
|
RawAppend(main_file, CreateProjectMain);
|
||||||
|
SaveBuffer(main_file);
|
||||||
|
|
||||||
|
OpenProject(project_dir);
|
||||||
|
Open(main_file->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
CO_OpenCode(co);
|
||||||
|
} RegisterCoroutineCommand(CO_CreateProject, "", "Creates a project in current buffer directory with template files and all that, asks user for input etc.");
|
||||||
1
src/text_editor/plugin_project_management.h
Normal file
1
src/text_editor/plugin_project_management.h
Normal file
@@ -0,0 +1 @@
|
|||||||
|
void SetProjectFolder(String name);
|
||||||
66
src/text_editor/plugin_record_events.cpp
Normal file
66
src/text_editor/plugin_record_events.cpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
Buffer *EventBuffer;
|
||||||
|
|
||||||
|
void Serialize(Buffer *buffer, String name, EventKind *kind) {
|
||||||
|
RawAppendf(buffer, "ev.%S = %s; ", name, EventKindStrings[*kind]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Serialize(Buffer *buffer, String name, Int *datum) {
|
||||||
|
if (*datum == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (name == "key") {
|
||||||
|
RawAppendf(buffer, "ev.%S = %s; ", name, SDLKeycodeToName((SDL_Keycode)*datum));
|
||||||
|
} else {
|
||||||
|
RawAppendf(buffer, "ev.%S = %lld; ", name, (long long)*datum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Serialize(Buffer *buffer, String name, uint32_t *datum) {
|
||||||
|
Int d = *datum;
|
||||||
|
Serialize(buffer, name, &d);
|
||||||
|
*datum = (uint32_t)d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Serialize(Buffer *buffer, String name, uint8_t *datum) {
|
||||||
|
Int d = *datum;
|
||||||
|
Serialize(buffer, name, &d);
|
||||||
|
*datum = (uint8_t)d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Serialize(Buffer *buffer, String name, int16_t *datum) {
|
||||||
|
Int d = *datum;
|
||||||
|
Serialize(buffer, name, &d);
|
||||||
|
*datum = (int16_t)d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Serialize(Buffer *buffer, String name, float *datum) {
|
||||||
|
if (*datum == 0.f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RawAppendf(buffer, "ev.%S = %f; ", name, *datum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Serialize(Buffer *buffer, String name, char **text) {
|
||||||
|
String str = *text;
|
||||||
|
if (str.len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RawAppendf(buffer, "ev.%S = \"%S\"; ", name, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerializeBegin(Buffer *buffer) {
|
||||||
|
RawAppendf(buffer, "{Event ev = {};");
|
||||||
|
}
|
||||||
|
|
||||||
|
void SerializeEnd(Buffer *buffer) {
|
||||||
|
RawAppendf(buffer, "Add(&EventPlayback, ev);}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// :Event
|
||||||
|
void Serialize(Buffer *buffer, Event *e) {
|
||||||
|
SerializeBegin(buffer);
|
||||||
|
#define X(TYPE, KIND, NAME) Serialize(buffer, #NAME, &e->NAME);
|
||||||
|
EVENT_FIELDS
|
||||||
|
#undef X
|
||||||
|
SerializeEnd(buffer);
|
||||||
|
}
|
||||||
1
src/text_editor/plugin_record_gc.cpp
Normal file
1
src/text_editor/plugin_record_gc.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Buffer *GCInfoBuffer;
|
||||||
2314
src/text_editor/plugin_remedybg.cpp
Normal file
2314
src/text_editor/plugin_remedybg.cpp
Normal file
File diff suppressed because it is too large
Load Diff
90
src/text_editor/plugin_search_open_buffers.cpp
Normal file
90
src/text_editor/plugin_search_open_buffers.cpp
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
struct SearchOpenBuffersParams {
|
||||||
|
String16 needle;
|
||||||
|
ViewID view;
|
||||||
|
};
|
||||||
|
|
||||||
|
void Coro_SearchOpenBuffers(mco_coro *co) {
|
||||||
|
CCtx *ctx = GetCoroutineContext();
|
||||||
|
SearchOpenBuffersParams *param = (SearchOpenBuffersParams *)ctx->user_ctx;
|
||||||
|
|
||||||
|
Array<BufferID> buffers = {ctx->arena};
|
||||||
|
For (Buffers) {
|
||||||
|
Add(&buffers, it->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForItem (id, buffers) {
|
||||||
|
Buffer *it = GetBuffer(id, NULL);
|
||||||
|
if (it == NULL || it->special || it->temp || it->dont_try_to_save_in_bulk_ops) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#if PLUGIN_DIRECTORY_NAVIGATION
|
||||||
|
if (it->is_dir) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{
|
||||||
|
Scratch scratch;
|
||||||
|
Array<Caret> occurences = FindAll(scratch, it, param->needle);
|
||||||
|
View *out_view = GetView(param->view);
|
||||||
|
ForItem (caret, occurences) {
|
||||||
|
Int pos = caret.range.min;
|
||||||
|
Int line = PosToLine(it, pos);
|
||||||
|
Range range = GetLineRangeWithoutNL(it, line);
|
||||||
|
Int column = pos - range.min;
|
||||||
|
String16 line_string = GetString(it, range);
|
||||||
|
String line_string8 = ToString(scratch, line_string);
|
||||||
|
Appendf(out_view, "%S ||> %S:%lld:%lld\n", line_string8, it->name, (long long)line + 1, (long long)column + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Yield(co);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateSearchOpenBuffersView() {
|
||||||
|
Scratch scratch;
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
String16 line_string = GetLineStringWithoutNL(active.buffer, 0);
|
||||||
|
uint64_t hash = HashBytes(line_string.data, line_string.len * sizeof(char16_t));
|
||||||
|
if (active.view->prev_search_line_hash != hash) {
|
||||||
|
active.view->prev_search_line_hash = hash;
|
||||||
|
if (line_string.len > 0) {
|
||||||
|
// @todo: somehow reintroduce history but manual, do the UndoKinds EditKind or something
|
||||||
|
// and implement EditKind_ExactBufferContent just save the
|
||||||
|
Caret caret = active.view->carets[0];
|
||||||
|
SelectEntireBuffer(active.view);
|
||||||
|
Replace(active.view, line_string);
|
||||||
|
Append(active.view, "\n", false);
|
||||||
|
|
||||||
|
RemoveCoroutine("Coro_SearchOpenBuffers");
|
||||||
|
CCtx *dat = AddCoroutine(Coro_SearchOpenBuffers);
|
||||||
|
SearchOpenBuffersParams *param = AllocType(dat->arena, SearchOpenBuffersParams);
|
||||||
|
param->needle = Copy16(dat->arena, line_string);
|
||||||
|
param->view = active.view->id;
|
||||||
|
dat->user_ctx = param;
|
||||||
|
dat->dont_wait_until_resolved = true;
|
||||||
|
ResumeCoroutine(dat);
|
||||||
|
|
||||||
|
active.view->carets[0] = caret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMD_SearchOpenBuffers() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
String16 string = {};
|
||||||
|
if (main.view->carets.len == 1 && GetSize(main.view->carets[0]) > 0) {
|
||||||
|
string = GetString(main.buffer, main.view->carets[0].range);
|
||||||
|
}
|
||||||
|
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
JumpTempBuffer(&main);
|
||||||
|
main.buffer->no_history = true;
|
||||||
|
AddCommand(&main.view->commands, "Open", OpenKeySet, CMD_OpenAndSetGotoNext);
|
||||||
|
main.view->update_hook = UpdateSearchOpenBuffersView;
|
||||||
|
if (string.len) {
|
||||||
|
SelectRange(main.view, GetLineRangeWithoutNL(main.buffer, 0));
|
||||||
|
Replace(main.view, string);
|
||||||
|
}
|
||||||
|
SelectRange(main.view, GetLineRangeWithoutNL(main.buffer, 0));
|
||||||
|
} RegisterCommand(CMD_SearchOpenBuffers, "ctrl-shift-f", "Interactive search over the entire project in a new buffer view");
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
|
Int SearchBufferChangeID;
|
||||||
|
|
||||||
void CMD_Search() {
|
void CMD_Search() {
|
||||||
BSet main = GetBSet(ActiveWindowID);
|
BSet main = GetBSet(ActiveWindowID);
|
||||||
String16 string = {};
|
String16 string = {};
|
||||||
|
main.window->search_bar_anchor = main.view->carets[0];
|
||||||
if (main.view->carets.len == 1 && GetSize(main.view->carets[0]) > 0) {
|
if (main.view->carets.len == 1 && GetSize(main.view->carets[0]) > 0) {
|
||||||
string = GetString(main.buffer, main.view->carets[0].range);
|
string = GetString(main.buffer, main.view->carets[0].range);
|
||||||
}
|
}
|
||||||
@@ -12,6 +15,7 @@ void CMD_Search() {
|
|||||||
Replace(set.view, string);
|
Replace(set.view, string);
|
||||||
SelectEntireBuffer(set.view);
|
SelectEntireBuffer(set.view);
|
||||||
}
|
}
|
||||||
|
SearchBufferChangeID = set.buffer->change_id;
|
||||||
} RegisterCommand(CMD_Search, "ctrl-f", "Open up a search window");
|
} RegisterCommand(CMD_Search, "ctrl-f", "Open up a search window");
|
||||||
|
|
||||||
void SearchWindowFindNext(bool forward = true) {
|
void SearchWindowFindNext(bool forward = true) {
|
||||||
@@ -19,8 +23,9 @@ void SearchWindowFindNext(bool forward = true) {
|
|||||||
BSet set = GetBSet(SearchWindowID);
|
BSet set = GetBSet(SearchWindowID);
|
||||||
String16 seek = GetString(set.buffer, GetRange(set.buffer));
|
String16 seek = GetString(set.buffer, GetRange(set.buffer));
|
||||||
Find(main.view, seek, forward);
|
Find(main.view, seek, forward);
|
||||||
main.window->search_bar_anchor = main.view->carets[0];
|
if (!IsMainCaretOnScreen(main.window).caret_on_screen) {
|
||||||
CenterView(PrimaryWindowID);
|
CenterView(main.window->id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMD_SearchNextInSearch() {
|
void CMD_SearchNextInSearch() {
|
||||||
@@ -55,33 +60,10 @@ void CMD_ToggleSearchWordBoundary() {
|
|||||||
SearchWordBoundary = !SearchWordBoundary;
|
SearchWordBoundary = !SearchWordBoundary;
|
||||||
} RegisterCommand(CMD_ToggleSearchWordBoundary, "alt-w", "Text editor wide search toggle, should apply to most search things");
|
} RegisterCommand(CMD_ToggleSearchWordBoundary, "alt-w", "Text editor wide search toggle, should apply to most search things");
|
||||||
|
|
||||||
void SearchWindowUpdate() {
|
void InitSearchWindow() {
|
||||||
BSet active = GetBSet(ActiveWindowID);
|
|
||||||
if (active.window->id == SearchWindowID && active.buffer->begin_frame_change_id != active.buffer->change_id) {
|
|
||||||
BSet main = GetBSet(PrimaryWindowID);
|
|
||||||
BSet set = GetBSet(SearchWindowID);
|
|
||||||
main.view->carets[0] = main.window->search_bar_anchor;
|
|
||||||
String16 seek = GetString(set.buffer, GetRange(set.buffer));
|
|
||||||
Find(main.view, seek, true);
|
|
||||||
CenterView(main.window->id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SearchWindowLayout(Rect2I *rect, Int wx, Int wy) {
|
|
||||||
Window *n = GetWindow(SearchWindowID);
|
|
||||||
Rect2I copy_rect = *rect;
|
|
||||||
if (!n->visible) {
|
|
||||||
rect = ©_rect;
|
|
||||||
}
|
|
||||||
Int barsize = GetExpandingBarSize(n);
|
|
||||||
n->document_rect = n->total_rect = CutBottom(rect, barsize);
|
|
||||||
n->line_numbers_rect = CutLeft(&n->document_rect, n->font->char_spacing * 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SearchWindowInit() {
|
|
||||||
Window *window = CreateWind();
|
Window *window = CreateWind();
|
||||||
SearchWindowID = window->id;
|
SearchWindowID = window->id;
|
||||||
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "search"));
|
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectFolder, "search"));
|
||||||
buffer->special = true;
|
buffer->special = true;
|
||||||
SearchBufferID = buffer->id;
|
SearchBufferID = buffer->id;
|
||||||
View *view = CreateView(buffer->id);
|
View *view = CreateView(buffer->id);
|
||||||
@@ -96,7 +78,35 @@ void SearchWindowInit() {
|
|||||||
window->visible = false;
|
window->visible = false;
|
||||||
window->lose_visibility_on_escape = true;
|
window->lose_visibility_on_escape = true;
|
||||||
window->jump_history = false;
|
window->jump_history = false;
|
||||||
AddHook(&view->hooks, "SearchAll", "alt-enter", CMD_SearchAll);
|
AddCommand(&view->commands, "SearchAll", AltEnterKeySet, CMD_SearchAll);
|
||||||
AddHook(&view->hooks, "SearchPrevInSearch", "shift-enter", CMD_SearchPrevInSearch);
|
AddCommand(&view->commands, "SearchPrevInSearch", ShiftEnterKeySet, CMD_SearchPrevInSearch);
|
||||||
AddHook(&view->hooks, "SearchNextInSearch", "enter", CMD_SearchNextInSearch);
|
AddCommand(&view->commands, "SearchNextInSearch", EnterKey, CMD_SearchNextInSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutSearchWindow(Rect2I *rect, int16_t wx, int16_t wy) {
|
||||||
|
Unused(wx); Unused(wy);
|
||||||
|
Window *window = GetWindow(SearchWindowID);
|
||||||
|
Rect2I copy_rect = *rect;
|
||||||
|
if (!window->visible) {
|
||||||
|
rect = ©_rect;
|
||||||
|
}
|
||||||
|
Int barsize = GetExpandingBarSize(window);
|
||||||
|
window->document_rect = window->total_rect = CutBottom(rect, barsize);
|
||||||
|
window->line_numbers_rect = CutLeft(&window->document_rect, window->font->char_spacing * 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateSearchWindow() {
|
||||||
|
if (ActiveWindowID == SearchWindowID) {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
if (SearchBufferChangeID != active.buffer->change_id) {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
main.view->carets[0] = main.window->search_bar_anchor;
|
||||||
|
String16 seek = GetString(active.buffer, GetRange(active.buffer));
|
||||||
|
Find(main.view, seek, true);
|
||||||
|
if (!IsMainCaretOnScreen(main.window).caret_on_screen) {
|
||||||
|
CenterView(main.window->id);
|
||||||
|
}
|
||||||
|
SearchBufferChangeID = active.buffer->change_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
3
src/text_editor/plugin_search_window.h
Normal file
3
src/text_editor/plugin_search_window.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
void InitSearchWindow();
|
||||||
|
void LayoutSearchWindow(Rect2I *rect, int16_t wx, int16_t wy);
|
||||||
|
void UpdateSearchWindow();
|
||||||
99
src/text_editor/plugin_status_window.cpp
Normal file
99
src/text_editor/plugin_status_window.cpp
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
WindowID StatusWindowID;
|
||||||
|
|
||||||
|
void InitStatusWindow() {
|
||||||
|
Window *window = CreateWind();
|
||||||
|
StatusWindowID = window->id;
|
||||||
|
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectFolder, "status_bar"));
|
||||||
|
buffer->special = true;
|
||||||
|
View *view = CreateView(buffer->id);
|
||||||
|
view->special = true;
|
||||||
|
window->active_view = view->id;
|
||||||
|
window->draw_line_numbers = false;
|
||||||
|
window->draw_scrollbar = false;
|
||||||
|
window->draw_line_highlight = true;
|
||||||
|
window->secondary_window_style = true;
|
||||||
|
window->primary = false;
|
||||||
|
window->jump_history = false;
|
||||||
|
window->lose_focus_on_escape = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutStatusWindow(Rect2I *rect, int16_t wx, int16_t wy) {
|
||||||
|
Unused(wx); Unused(wy);
|
||||||
|
Window *window = GetWindow(StatusWindowID);
|
||||||
|
Rect2I copy_rect = *rect;
|
||||||
|
if (!window->visible) {
|
||||||
|
rect = ©_rect;
|
||||||
|
}
|
||||||
|
Int barsize = GetExpandingBarSize(window);
|
||||||
|
window->document_rect = window->total_rect = CutBottom(rect, barsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateStatusWindow() {
|
||||||
|
ProfileFunction();
|
||||||
|
Scratch scratch;
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
BSet title = GetBSet(StatusWindowID);
|
||||||
|
title.view->scroll.y = 0;
|
||||||
|
if (title.window->id == ActiveWindowID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
Vec2I doc_rect = GetSize(title.window->document_rect);
|
||||||
|
Int width_in_chars = doc_rect.x / title.window->font->char_spacing;
|
||||||
|
|
||||||
|
char buff[1024];
|
||||||
|
for (int i = 0; i < 1024; i += 1) buff[i] = ' ';
|
||||||
|
|
||||||
|
RawReplaceText(title.buffer, GetRange(title.buffer), u"");
|
||||||
|
RawAppendf(title.buffer, "%.*s", width_in_chars - 1, buff);
|
||||||
|
RawAppendf(title.buffer, "@");
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
String16 buffer_string = GetString(title.buffer);
|
||||||
|
Range replace_range = {0, title.buffer->len};
|
||||||
|
Seek(buffer_string, u"\n", &replace_range.max);
|
||||||
|
|
||||||
|
Caret caret = main.view->carets[0];
|
||||||
|
XY xy = PosToXY(main.buffer, GetFront(caret));
|
||||||
|
|
||||||
|
String indicators = "";
|
||||||
|
if (main.buffer->dirty) {
|
||||||
|
indicators = Format(scratch, "%S!", indicators);
|
||||||
|
}
|
||||||
|
if (SearchCaseSensitive) {
|
||||||
|
indicators = Format(scratch, "%SC", indicators);
|
||||||
|
}
|
||||||
|
if (SearchWordBoundary) {
|
||||||
|
indicators = Format(scratch, "%SW", indicators);
|
||||||
|
}
|
||||||
|
|
||||||
|
String commands = Format(scratch, ":Errors[%d]", ErrorCount);
|
||||||
|
if (main.buffer->changed_on_disk) {
|
||||||
|
commands = Format(scratch, "%S :Reopen", commands);
|
||||||
|
}
|
||||||
|
For (ActiveProcesses) {
|
||||||
|
if (it.args.output_view == main.view->id) {
|
||||||
|
commands = Format(scratch, "%S :KillProcess", commands);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String s = Format(scratch, "%S:%lld:%lld %S | :Prev :Next :Close %S", main.buffer->name, (long long)xy.line + 1ll, (long long)xy.col + 1ll, indicators, commands);
|
||||||
|
For (ActiveProcesses) {
|
||||||
|
if (it.args.output_view == main.view->id) {
|
||||||
|
s = Format(scratch, "%S %lld :KillProcess", s, (long long)it.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String16 string = ToString16(scratch, s);
|
||||||
|
String16 string_to_replace = GetString(title.buffer, replace_range);
|
||||||
|
if (string_to_replace != string) {
|
||||||
|
SelectRange(title.view, replace_range);
|
||||||
|
ReplaceEx(scratch, title.view, string);
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectRange(title.view, MakeRange(0));
|
||||||
|
ResetHistory(title.buffer);
|
||||||
|
}
|
||||||
82
src/text_editor/plugin_window_management.cpp
Normal file
82
src/text_editor/plugin_window_management.cpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
void CMD_JumpPrev() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
JumpToLastValidView(main.window);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
} RegisterCommand(CMD_JumpPrev, "ctrl-tab", "Go to the previous open view in primary window");
|
||||||
|
|
||||||
|
void CMD_Prev() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
main.window->skip_checkpoint = true;
|
||||||
|
JumpBack(main.window);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
} RegisterCommand(CMD_Prev, "alt-q | mousex1", "Go to previous position (either previous view that was open or caret position) in the primary window");
|
||||||
|
|
||||||
|
void CMD_Next() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
main.window->skip_checkpoint = true;
|
||||||
|
JumpForward(main.window);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
} RegisterCommand(CMD_Next, "alt-shift-q | mousex2", "Go to next position, after backtracking, in the primary window");
|
||||||
|
|
||||||
|
void CMD_FocusLeftWindow() {
|
||||||
|
NextActiveWindowID = SwitchWindow(DIR_LEFT)->id;
|
||||||
|
} RegisterCommand(CMD_FocusLeftWindow, "alt-left", "Switch the window focus to the window on the left position of current one");
|
||||||
|
|
||||||
|
void CMD_FocusRightWindow() {
|
||||||
|
NextActiveWindowID = SwitchWindow(DIR_RIGHT)->id;
|
||||||
|
} RegisterCommand(CMD_FocusRightWindow, "alt-right", "Switch the window focus to the window on the right position of current one");
|
||||||
|
|
||||||
|
void CMD_FocusWindow1() {
|
||||||
|
NextActiveWindowID = GetOverlappingWindow({0,0}, GetWindow(ActiveWindowID))->id;
|
||||||
|
} RegisterCommand(CMD_FocusWindow1, "ctrl-1", "Select the left-most window");
|
||||||
|
|
||||||
|
void CMD_FocusWindow2() {
|
||||||
|
Window *first = GetOverlappingWindow({0,0}, GetWindow(ActiveWindowID));
|
||||||
|
Vec2I p = GetSideOfWindow(first, DIR_RIGHT);
|
||||||
|
NextActiveWindowID = GetOverlappingWindow(p, GetWindow(ActiveWindowID))->id;
|
||||||
|
} RegisterCommand(CMD_FocusWindow2, "ctrl-2", "Select the window to the right of left-most window");
|
||||||
|
|
||||||
|
void CMD_FocusWindow3() {
|
||||||
|
Window *first = GetOverlappingWindow({0,0});
|
||||||
|
if (first) {
|
||||||
|
Window *second = GetOverlappingWindow(GetSideOfWindow(first, DIR_RIGHT));
|
||||||
|
if (second) {
|
||||||
|
Window *third = GetOverlappingWindow(GetSideOfWindow(second, DIR_RIGHT));
|
||||||
|
if (third) {
|
||||||
|
NextActiveWindowID = third->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} RegisterCommand(CMD_FocusWindow3, "ctrl-3", "Select the 3rd window, counting from left");
|
||||||
|
|
||||||
|
void CMD_FocusWindow4() {
|
||||||
|
Window *first = GetOverlappingWindow({0,0});
|
||||||
|
if (first) {
|
||||||
|
Window *second = GetOverlappingWindow(GetSideOfWindow(first, DIR_RIGHT));
|
||||||
|
if (second) {
|
||||||
|
Window *third = GetOverlappingWindow(GetSideOfWindow(second, DIR_RIGHT));
|
||||||
|
if (third) {
|
||||||
|
Window *fourth = GetOverlappingWindow(GetSideOfWindow(third, DIR_RIGHT));
|
||||||
|
if (fourth) NextActiveWindowID = fourth->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} RegisterCommand(CMD_FocusWindow4, "ctrl-4", "Select the 4th window, counting from left");
|
||||||
|
|
||||||
|
void CMD_NewWindow() {
|
||||||
|
CreateWind();
|
||||||
|
} RegisterCommand(CMD_NewWindow, "ctrl-backslash", "Creates a new window");
|
||||||
|
|
||||||
|
void CMD_CloseWindow() {
|
||||||
|
Close(PrimaryWindowID);
|
||||||
|
} RegisterCommand(CMD_CloseWindow, "", "Close the last active primary window");
|
||||||
|
|
||||||
|
void CMD_GotoNextInList() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
GotoNextInList(main.window, 1);
|
||||||
|
} RegisterCommand(CMD_GotoNextInList, "ctrl-e", "For example: when jumping from build panel to build error, a jump point is setup, user can click this button to go over to the next compiler error");
|
||||||
|
|
||||||
|
void CMD_GotoPrevInList() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
GotoNextInList(main.window, -1);
|
||||||
|
} RegisterCommand(CMD_GotoPrevInList, "alt-e", "For example: when jumping from build panel to build error, a jump point is setup, user can click this button to go over to the previous compiler error");
|
||||||
252
src/text_editor/plugin_word_complete.cpp
Normal file
252
src/text_editor/plugin_word_complete.cpp
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
// MAAAAAAAAAAAAN I DONT LIKE THIS CODE, BUT HOPE IT WORKS
|
||||||
|
|
||||||
|
// @todo: potentially bad that we are not checking against end! lexer->at[0] == 0 check is not enough
|
||||||
|
struct Lexer2 {
|
||||||
|
char16_t *at;
|
||||||
|
};
|
||||||
|
|
||||||
|
Lexer2 BeginLexing(String16 data) {
|
||||||
|
Lexer2 result = {data.data};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String16 Next(Lexer2 *lexer) {
|
||||||
|
while (lexer->at[0] != 0 && IsNonWord(lexer->at[0])) {
|
||||||
|
lexer->at += 1;
|
||||||
|
}
|
||||||
|
String16 result = {lexer->at, 0};
|
||||||
|
while (lexer->at[0] != 0 && IsWord(lexer->at[0])) {
|
||||||
|
lexer->at += 1;
|
||||||
|
result.len += 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StringAndDistance {
|
||||||
|
String16 string;
|
||||||
|
Int distance;
|
||||||
|
};
|
||||||
|
|
||||||
|
Int FindValueIndex(Array<StringAndDistance> *arr, String16 string) {
|
||||||
|
for (int64_t i = 0; i < arr->len; i += 1) {
|
||||||
|
if (arr->data[i].string == string) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool MergeSortCompare(StringAndDistance *EntryA, StringAndDistance *EntryB) {
|
||||||
|
bool result = EntryA->distance > EntryB->distance;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct {
|
||||||
|
BlockArena arena;
|
||||||
|
String16 prefix_string;
|
||||||
|
String16 last_string;
|
||||||
|
mco_coro *co;
|
||||||
|
Buffer *buffer;
|
||||||
|
View *view;
|
||||||
|
Int original_caret_pos;
|
||||||
|
Int direction;
|
||||||
|
} CWS;
|
||||||
|
|
||||||
|
void CWSLexIdentifiers(Array<StringAndDistance> *out_idents, Buffer *buffer) {
|
||||||
|
Array<StringAndDistance> idents = {CWS.arena};
|
||||||
|
String16 string = GetString(buffer);
|
||||||
|
Lexer2 lexer = BeginLexing(string.data);
|
||||||
|
for (;;) {
|
||||||
|
String16 token = Next(&lexer);
|
||||||
|
if (token.len <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (StartsWith(token, CWS.prefix_string) && token != CWS.prefix_string) {
|
||||||
|
Int pos = token.data - buffer->str;
|
||||||
|
// Here we are computing distance based on position from currently open
|
||||||
|
// buffer but should be fine. We are sorting then putting into the array
|
||||||
|
// and it doesn't displace the original ones so it's fine
|
||||||
|
Int distance = Absolute(CWS.original_caret_pos - pos);
|
||||||
|
int64_t value_index = FindValueIndex(&idents, token);
|
||||||
|
if (value_index != -1) {
|
||||||
|
if (idents[value_index].distance > distance) {
|
||||||
|
idents[value_index].distance = distance;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Add(&idents, {Copy16(CWS.arena, token), distance});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idents.len > 1) {
|
||||||
|
Array<StringAndDistance> temp = TightCopy(CWS.arena, idents);
|
||||||
|
MergeSort(idents.len, idents.data, temp.data);
|
||||||
|
}
|
||||||
|
For (idents) {
|
||||||
|
if (FindValueIndex(out_idents, it.string) == -1) Add(out_idents, it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
void CWSGetNextBuffer(mco_coro *co) {
|
||||||
|
// Would be nice to iterate through 64 last active buffers
|
||||||
|
// - Then all buffers
|
||||||
|
Add(&CWS.visited_buffers, CWS.buffer->id);
|
||||||
|
mco_result res = mco_push(co, &CWS.buffer->id, sizeof(CWS.buffer->id));
|
||||||
|
Assert(res == MCO_SUCCESS);
|
||||||
|
mco_yield(co);
|
||||||
|
|
||||||
|
For (IterateInReverse(&Buffers)) {
|
||||||
|
Add(&buffers, it->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
ForItem (window, Windows) {
|
||||||
|
if (!window->visible) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
View *view = GetView(window->active_view);
|
||||||
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
|
if (Contains(CWS.visited_buffers, buffer->id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(&CWS.visited_buffers, buffer->id);
|
||||||
|
mco_result res = mco_push(co, &buffer->id, sizeof(buffer->id));
|
||||||
|
Assert(res == MCO_SUCCESS);
|
||||||
|
mco_yield(co);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void WordComplete(mco_coro *co) {
|
||||||
|
Array<BufferID> buffers = {CWS.arena};
|
||||||
|
Add(&buffers, CWS.buffer->id);
|
||||||
|
For (IterateInReverse(&Buffers)) {
|
||||||
|
Add(&buffers, it->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Int buffer_i = 0;
|
||||||
|
Array<StringAndDistance> idents = {CWS.arena};
|
||||||
|
Add(&idents, {CWS.prefix_string, 0});
|
||||||
|
for (Int i = 1;;) {
|
||||||
|
if (i >= idents.len) {
|
||||||
|
while (buffer_i < buffers.len) {
|
||||||
|
Buffer *buffer = GetBuffer(buffers[buffer_i++]);
|
||||||
|
CWSLexIdentifiers(&idents, buffer);
|
||||||
|
if (i < idents.len) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i >= idents.len) {
|
||||||
|
goto yield;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CWS.last_string = Copy16(CWS.arena, idents[i].string);
|
||||||
|
SelectRange(CWS.view, EncloseWord(CWS.buffer, CWS.original_caret_pos));
|
||||||
|
Replace(CWS.view, CWS.last_string);
|
||||||
|
yield:;
|
||||||
|
mco_yield(co);
|
||||||
|
if (CWS.direction == -1 && i > 0 && i == idents.len) {
|
||||||
|
// special case for when we are at the end of the list and we want to go back
|
||||||
|
// to make it sure we don't need to click twice to flip
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
i += CWS.direction;
|
||||||
|
i = Clamp(i, (Int)0, idents.len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WordComplete(View *view, Int pos) {
|
||||||
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
|
Range prefix_string_range = {GetWordStart(buffer, pos), pos};
|
||||||
|
String16 prefix = GetString(buffer, prefix_string_range);
|
||||||
|
if (prefix == u"") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool continue_with_previous = CWS.co && CWS.last_string == prefix && CWS.buffer == buffer && CWS.view == view;
|
||||||
|
if (!continue_with_previous) {
|
||||||
|
if (CWS.co) {
|
||||||
|
mco_result res = mco_destroy(CWS.co);
|
||||||
|
Assert(res == MCO_SUCCESS);
|
||||||
|
CWS.co = NULL;
|
||||||
|
}
|
||||||
|
Release(&CWS.arena);
|
||||||
|
MemoryZero(&CWS, sizeof(CWS));
|
||||||
|
|
||||||
|
// CWS.visited_buffers.allocator = CWS.arena;
|
||||||
|
CWS.buffer = buffer;
|
||||||
|
CWS.view = view;
|
||||||
|
CWS.original_caret_pos = pos;
|
||||||
|
CWS.prefix_string = Copy16(CWS.arena, prefix);
|
||||||
|
|
||||||
|
mco_desc desc = mco_desc_init(WordComplete, 0);
|
||||||
|
mco_result res = mco_create(&CWS.co, &desc);
|
||||||
|
Assert(res == MCO_SUCCESS);
|
||||||
|
}
|
||||||
|
CWS.direction = 1;
|
||||||
|
mco_result res = mco_resume(CWS.co);
|
||||||
|
Assert(res == MCO_SUCCESS);
|
||||||
|
|
||||||
|
if (mco_status(CWS.co) == MCO_DEAD) {
|
||||||
|
res = mco_destroy(CWS.co);
|
||||||
|
Assert(res == MCO_SUCCESS);
|
||||||
|
CWS.co = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CompletePrevWord(View *view, Int pos) {
|
||||||
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
|
Range prefix_string_range = {GetWordStart(buffer, pos), pos};
|
||||||
|
String16 prefix = GetString(buffer, prefix_string_range);
|
||||||
|
bool continue_with_previous = CWS.co && CWS.last_string == prefix && CWS.buffer == buffer;
|
||||||
|
if (!continue_with_previous) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CWS.direction = -1;
|
||||||
|
mco_result res = mco_resume(CWS.co);
|
||||||
|
Assert(res == MCO_SUCCESS);
|
||||||
|
if (mco_status(CWS.co) == MCO_DEAD) {
|
||||||
|
res = mco_destroy(CWS.co);
|
||||||
|
Assert(res == MCO_SUCCESS);
|
||||||
|
CWS.co = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CMD_WordComplete() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
bool ok = active.view->carets.len == 1 && GetSize(active.view->carets[0].range) == 0;
|
||||||
|
if (!ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WordComplete(active.view, active.view->carets[0].range.min);
|
||||||
|
} RegisterCommand(CMD_WordComplete, "ctrl-space", "Completes the current word");
|
||||||
|
|
||||||
|
void CMD_CompletePrevWord() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
bool ok = active.view->carets.len == 1 && GetSize(active.view->carets[0].range) == 0;
|
||||||
|
if (!ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CompletePrevWord(active.view, active.view->carets[0].range.min);
|
||||||
|
} RegisterCommand(CMD_CompletePrevWord, "ctrl-shift-space", "If already completing a word, iterate to previous word");
|
||||||
|
|
||||||
|
void CMD_CompletePrevWordOrDedent() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
if (active.view->carets.len == 1 && GetSize(active.view->carets[0].range) == 0) {
|
||||||
|
CMD_CompletePrevWord();
|
||||||
|
} else {
|
||||||
|
IndentSelectedLines(active.view, true);
|
||||||
|
}
|
||||||
|
} RegisterCommand(CMD_CompletePrevWordOrDedent, "", "Completes the current word or it indents it, when single caret with no selection it goes for word complete");
|
||||||
|
|
||||||
|
void CMD_WordCompleteOrIndent() {
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
if (active.view->carets.len == 1 && GetSize(active.view->carets[0].range) == 0) {
|
||||||
|
CMD_WordComplete();
|
||||||
|
} else {
|
||||||
|
IndentSelectedLines(active.view);
|
||||||
|
}
|
||||||
|
} RegisterCommand(CMD_WordCompleteOrIndent, "", "Completes the current word or it indents it, when single caret with no selection it goes for word complete");
|
||||||
@@ -1,3 +1,456 @@
|
|||||||
|
#if OS_WINDOWS
|
||||||
|
struct Win32Process {
|
||||||
|
HANDLE handle;
|
||||||
|
HANDLE child_stdout_read;
|
||||||
|
HANDLE child_stdout_write;
|
||||||
|
HANDLE child_stdin_read;
|
||||||
|
HANDLE child_stdin_write;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Win32Process) < sizeof(Process::platform));
|
||||||
|
|
||||||
|
static void Win32ReportError(String msg, String cmd) {
|
||||||
|
LPVOID lpMsgBuf;
|
||||||
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL);
|
||||||
|
defer { LocalFree(lpMsgBuf); };
|
||||||
|
char *buff = (char *)lpMsgBuf;
|
||||||
|
size_t buffLen = strlen((const char *)buff);
|
||||||
|
if (buffLen > 0 && buff[buffLen - 1] == '\n') buff[buffLen - 1] = 0;
|
||||||
|
Error("%S: %S! %s", msg, cmd, (char *)lpMsgBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Win32CloseProcess(Process *process) {
|
||||||
|
Win32Process *p = (Win32Process *)process->platform;
|
||||||
|
if (p->handle != INVALID_HANDLE_VALUE) CloseHandle(p->handle);
|
||||||
|
if (p->child_stdout_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_write);
|
||||||
|
if (p->child_stdout_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_read);
|
||||||
|
if (p->child_stdin_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_read);
|
||||||
|
if (p->child_stdin_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_write);
|
||||||
|
process->is_valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Win32ProcessError(Process *process, String msg, String cmd) {
|
||||||
|
Win32ReportError(msg, cmd);
|
||||||
|
Win32CloseProcess(process);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteStdin(Process *process, String string) {
|
||||||
|
if (string.len == 0) return;
|
||||||
|
Assert(process->is_valid);
|
||||||
|
|
||||||
|
Win32Process *p = (Win32Process *)process->platform;
|
||||||
|
Assert(p->child_stdin_write != INVALID_HANDLE_VALUE);
|
||||||
|
|
||||||
|
DWORD written = 0;
|
||||||
|
bool write_error = WriteFile(p->child_stdin_write, string.data, (DWORD)string.len, &written, NULL) == 0;
|
||||||
|
|
||||||
|
Assert(write_error == false);
|
||||||
|
Assert(written == string.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CloseStdin(Process *process) {
|
||||||
|
Win32Process *p = (Win32Process *)process->platform;
|
||||||
|
CloseHandle(p->child_stdin_write);
|
||||||
|
p->child_stdin_write = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Process SpawnProcess(ExecArgs args) {
|
||||||
|
Scratch scratch;
|
||||||
|
Process process = {};
|
||||||
|
process.args = args;
|
||||||
|
Win32Process *p = (Win32Process *)process.platform;
|
||||||
|
|
||||||
|
p->handle = INVALID_HANDLE_VALUE;
|
||||||
|
p->child_stdout_write = INVALID_HANDLE_VALUE;
|
||||||
|
p->child_stdout_read = INVALID_HANDLE_VALUE;
|
||||||
|
p->child_stdin_read = INVALID_HANDLE_VALUE;
|
||||||
|
p->child_stdin_write = INVALID_HANDLE_VALUE;
|
||||||
|
|
||||||
|
SECURITY_ATTRIBUTES security_atrb = {};
|
||||||
|
security_atrb.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||||
|
security_atrb.bInheritHandle = TRUE;
|
||||||
|
|
||||||
|
if (!CreatePipe(&p->child_stdout_read, &p->child_stdout_write, &security_atrb, 0)) {
|
||||||
|
Win32ProcessError(&process, "Failed to create process at create pipe stage", args.cmd);
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetHandleInformation(p->child_stdout_read, HANDLE_FLAG_INHERIT, 0)) {
|
||||||
|
Win32ProcessError(&process, "Failed to create process at create pipe stage", args.cmd);
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.open_stdin) {
|
||||||
|
if (!CreatePipe(&p->child_stdin_read, &p->child_stdin_write, &security_atrb, 0)) {
|
||||||
|
Win32ProcessError(&process, "Failed to create process at create pipe stage", args.cmd);
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetHandleInformation(p->child_stdin_write, HANDLE_FLAG_INHERIT, 0)) {
|
||||||
|
Win32ProcessError(&process, "Failed to create process at create pipe stage", args.cmd);
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
STARTUPINFOW startup = {};
|
||||||
|
startup.cb = sizeof(STARTUPINFO);
|
||||||
|
startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
|
||||||
|
startup.hStdOutput = p->child_stdout_write;
|
||||||
|
startup.hStdError = p->child_stdout_write;
|
||||||
|
startup.wShowWindow = SW_HIDE;
|
||||||
|
if (args.open_stdin) {
|
||||||
|
startup.hStdInput = p->child_stdin_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
String16 cwd = ToString16(scratch, args.cwd);
|
||||||
|
String16 cmd = ToString16(scratch, args.cmd);
|
||||||
|
String16 exe = ToString16(scratch, args.exe);
|
||||||
|
|
||||||
|
char *env = NULL;
|
||||||
|
if (args.env.len) {
|
||||||
|
Int size = GetSize(args.env) + args.env.len + 1;
|
||||||
|
env = AllocArray(scratch, char, size);
|
||||||
|
Int i = 0;
|
||||||
|
For (args.env) {
|
||||||
|
MemoryCopy(env + i, it.data, it.len);
|
||||||
|
i += it.len;
|
||||||
|
|
||||||
|
env[i++] = 0;
|
||||||
|
}
|
||||||
|
env[i++] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD dwCreationFlags = 0;
|
||||||
|
BOOL bInheritHandles = TRUE;
|
||||||
|
|
||||||
|
PROCESS_INFORMATION info = {};
|
||||||
|
if (!CreateProcessW((wchar_t *)exe.data, (wchar_t *)cmd.data, 0, 0, bInheritHandles, dwCreationFlags, env, (wchar_t *)cwd.data, &startup, &info)) {
|
||||||
|
Win32ProcessError(&process, "failed to create process", args.cmd);
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close handles to the stdin and stdout pipes no longer needed by the child process.
|
||||||
|
// If they are not explicitly closed, there is no way to recognize that the child process has ended.
|
||||||
|
CloseHandle(info.hThread);
|
||||||
|
CloseHandle(p->child_stdout_write);
|
||||||
|
p->child_stdout_write = INVALID_HANDLE_VALUE;
|
||||||
|
|
||||||
|
p->handle = info.hProcess;
|
||||||
|
process.is_valid = true;
|
||||||
|
process.id = (int64_t)p->handle;
|
||||||
|
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsValid(Process *process) {
|
||||||
|
if (process->is_valid == false) return false;
|
||||||
|
Win32Process *p = (Win32Process *)process->platform;
|
||||||
|
|
||||||
|
if (WaitForSingleObject(p->handle, 0) != WAIT_OBJECT_0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD exit_code;
|
||||||
|
bool get_exit_code_failed = GetExitCodeProcess(p->handle, &exit_code) == 0;
|
||||||
|
if (get_exit_code_failed) {
|
||||||
|
exit_code = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
process->exit_code = (int)exit_code;
|
||||||
|
Win32CloseProcess(process);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KillProcess(Process *process) {
|
||||||
|
Assert(process->is_valid);
|
||||||
|
Win32Process *p = (Win32Process *)process->platform;
|
||||||
|
|
||||||
|
bool terminate_process_error = TerminateProcess(p->handle, -1) == 0;
|
||||||
|
if (terminate_process_error) {
|
||||||
|
Assert(0);
|
||||||
|
}
|
||||||
|
Win32CloseProcess(process);
|
||||||
|
process->exit_code = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
String PollStdout(Allocator allocator, Process *process, bool force_read) {
|
||||||
|
Assert(process->is_valid);
|
||||||
|
Win32Process *p = (Win32Process *)process->platform;
|
||||||
|
|
||||||
|
DWORD bytes_avail = 0;
|
||||||
|
bool peek_error = PeekNamedPipe(p->child_stdout_read, NULL, 0, NULL, &bytes_avail, NULL) == 0;
|
||||||
|
if (peek_error) {
|
||||||
|
return {};
|
||||||
|
} else if (bytes_avail == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t buffer_size = ClampTop(bytes_avail, (DWORD)(4096 * 16));
|
||||||
|
char *buffer = AllocArray(allocator, char, buffer_size);
|
||||||
|
|
||||||
|
DWORD bytes_read = 0;
|
||||||
|
bool read_error = ReadFile(p->child_stdout_read, buffer, (DWORD)buffer_size, &bytes_read, 0) == 0;
|
||||||
|
if (read_error) {
|
||||||
|
Win32ReportError("Failed to read the stdout of child process", "ReadFile");
|
||||||
|
Dealloc(allocator, buffer);
|
||||||
|
Win32CloseProcess(process);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = {buffer, bytes_read};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif OS_POSIX
|
||||||
|
|
||||||
|
struct UnixProcess {
|
||||||
|
pid_t pid;
|
||||||
|
int child_stdout_read;
|
||||||
|
int stdin_write;
|
||||||
|
};
|
||||||
|
|
||||||
|
Array<char *> SplitCommand(Allocator allocator, String command_line) {
|
||||||
|
Array<char *> cmd = {allocator};
|
||||||
|
|
||||||
|
String curr = {};
|
||||||
|
for (int i = 0; i < command_line.len; i += 1) {
|
||||||
|
if (command_line.data[i] == ' ') {
|
||||||
|
if (curr.len > 0) {
|
||||||
|
Add(&cmd, Copy(allocator, curr).data);
|
||||||
|
curr = {};
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (curr.len == 0) {
|
||||||
|
curr.data = command_line.data + i;
|
||||||
|
}
|
||||||
|
curr.len += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr.len > 0) {
|
||||||
|
Add(&cmd, Copy(allocator, curr).data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array<String> enviroment) {
|
||||||
|
Scratch scratch;
|
||||||
|
const int PIPE_READ = 0;
|
||||||
|
const int PIPE_WRITE = 1;
|
||||||
|
bool error = false;
|
||||||
|
|
||||||
|
working_dir = Copy(scratch, working_dir);
|
||||||
|
chdir(working_dir.data);
|
||||||
|
|
||||||
|
Process process = {};
|
||||||
|
process.args = args;
|
||||||
|
UnixProcess *plat = (UnixProcess *)&process.platform;
|
||||||
|
Array<char *> args = SplitCommand(scratch, command_line);
|
||||||
|
Array<char *> env = {scratch};
|
||||||
|
|
||||||
|
For (enviroment) {
|
||||||
|
Add(&env, Copy(scratch, it).data);
|
||||||
|
}
|
||||||
|
|
||||||
|
int stdout_desc[2] = {};
|
||||||
|
int stdin_desc[2] = {};
|
||||||
|
posix_spawn_file_actions_t actions = {};
|
||||||
|
|
||||||
|
if (posix_spawn_file_actions_init(&actions) != 0) {
|
||||||
|
Error("Libc function failed: posix_spawn_file_actions_init, with error: %s", strerror(errno));
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
posix_spawn_file_actions_destroy(&actions);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pipe(stdout_desc) == -1) {
|
||||||
|
Error("Libc function failed: pipe, with error: %s", strerror(errno));
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
if (error) {
|
||||||
|
close(stdout_desc[PIPE_READ]);
|
||||||
|
close(stdout_desc[PIPE_WRITE]);
|
||||||
|
} else {
|
||||||
|
close(stdout_desc[PIPE_WRITE]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pipe(stdin_desc) == -1) {
|
||||||
|
Error("Libc function failed: pipe, with error: %s", strerror(errno));
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
if (error) {
|
||||||
|
close(stdin_desc[PIPE_READ]);
|
||||||
|
close(stdin_desc[PIPE_WRITE]);
|
||||||
|
} else {
|
||||||
|
close(stdin_desc[PIPE_READ]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
error = posix_spawn_file_actions_addclose(&actions, stdout_desc[PIPE_READ]) != 0;
|
||||||
|
if (error) {
|
||||||
|
Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno));
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDOUT_FILENO) != 0;
|
||||||
|
if (error) {
|
||||||
|
Error("Libc function failed: posix_spawn_file_actions_adddup2 STDOUT_FILENO, with error: %s", strerror(errno));
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDERR_FILENO) != 0;
|
||||||
|
if (error) {
|
||||||
|
Error("Libc function failed: posix_spawn_file_actions_adddup2 STDERR_FILENO, with error: %s", strerror(errno));
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = posix_spawn_file_actions_addclose(&actions, stdin_desc[PIPE_WRITE]) != 0;
|
||||||
|
if (error) {
|
||||||
|
Error("Libc function failed: posix_spawn_file_actions_addclose, with error: %s", strerror(errno));
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = posix_spawn_file_actions_adddup2(&actions, stdin_desc[PIPE_READ], STDIN_FILENO) != 0;
|
||||||
|
if (error) {
|
||||||
|
Error("Libc function failed: posix_spawn_file_actions_adddup2 STDIN_FILENO, with error: %s", strerror(errno));
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t process_pid = 0;
|
||||||
|
error = posix_spawnp(&process_pid, args[0], &actions, NULL, args.data, env.data) != 0;
|
||||||
|
if (error) {
|
||||||
|
Error("Libc function failed: failed to create process\n, with error: %s", strerror(errno));
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
plat->child_stdout_read = stdout_desc[PIPE_READ];
|
||||||
|
plat->stdin_write = stdin_desc[PIPE_WRITE];
|
||||||
|
plat->pid = process_pid;
|
||||||
|
|
||||||
|
if (write_stdin.len) {
|
||||||
|
WriteStdin(&process, write_stdin);
|
||||||
|
CloseStdin(&process);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.id = process_pid;
|
||||||
|
process.is_valid = true;
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsValid(Process *process) {
|
||||||
|
UnixProcess *plat = (UnixProcess *)&process->platform;
|
||||||
|
if (process->is_valid == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = 0;
|
||||||
|
pollfd p = {};
|
||||||
|
p.fd = plat->child_stdout_read;
|
||||||
|
p.events = POLLRDHUP | POLLERR | POLLHUP | POLLNVAL;
|
||||||
|
int res = poll(&p, 1, 0);
|
||||||
|
if (res > 0) {
|
||||||
|
pid_t result = waitpid(plat->pid, &status, 0);
|
||||||
|
Assert(result != -1);
|
||||||
|
process->exit_code = WEXITSTATUS(status);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KillProcess(Process *process) {
|
||||||
|
Assert(process->is_valid);
|
||||||
|
UnixProcess *plat = (UnixProcess *)process->platform;
|
||||||
|
kill(plat->pid, SIGKILL);
|
||||||
|
process->exit_code = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
String PollStdout(Allocator allocator, Process *process, bool force_read) {
|
||||||
|
Assert(process->is_valid);
|
||||||
|
UnixProcess *plat = (UnixProcess *)process->platform;
|
||||||
|
|
||||||
|
String result = {};
|
||||||
|
result.data = AllocArray(allocator, char, 16 * 4096);
|
||||||
|
|
||||||
|
pollfd p = {};
|
||||||
|
p.fd = plat->child_stdout_read;
|
||||||
|
p.events = POLLIN;
|
||||||
|
int res = poll(&p, 1, 0);
|
||||||
|
if (res > 0 || force_read) {
|
||||||
|
result.len = read(plat->child_stdout_read, result.data, 4 * 4096);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteStdin(Process *process, String string) {
|
||||||
|
if (string.len == 0) return;
|
||||||
|
Assert(process->is_valid);
|
||||||
|
|
||||||
|
UnixProcess *plat = (UnixProcess *)process->platform;
|
||||||
|
ssize_t size = write(plat->stdin_write, string.data, string.len);
|
||||||
|
|
||||||
|
Assert(size == string.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CloseStdin(Process *process) {
|
||||||
|
UnixProcess *plat = (UnixProcess *)process->platform;
|
||||||
|
close(plat->stdin_write);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array<String> enviroment) { return {}; }
|
||||||
|
bool IsValid(Process *process) { return false; }
|
||||||
|
void KillProcess(Process *process) { }
|
||||||
|
String PollStdout(Allocator allocator, Process *process, bool force_read) { return {}; }
|
||||||
|
void WriteStdin(Process *process, String string) { return; }
|
||||||
|
void CloseStdin(Process *process) { return; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// @todo: perhaps rework the PATH ProcessEnviroment into an object from data_desc
|
||||||
|
String GetEnv(String name) {
|
||||||
|
For (ProcessEnviroment) {
|
||||||
|
if (StartsWith(it, name, true)) {
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
String FindPython(Allocator allocator) {
|
||||||
|
Scratch scratch(allocator);
|
||||||
|
String path = GetEnv("PATH="); // ignore case on windows so it's fine. 'Path=' not needed
|
||||||
|
Assert(path.len);
|
||||||
|
String delim = IF_OS_WINDOWS_ELSE(";", ":");
|
||||||
|
Array<String> paths = Split(scratch, path, delim);
|
||||||
|
String tries[] = {"python3", "python", "py"};
|
||||||
|
for (int i = 0; i < Lengthof(tries); i += 1) {
|
||||||
|
For (paths) {
|
||||||
|
String path_it = Format(scratch, "%S/%S" IF_OS_WINDOWS_ELSE(".exe", ""), it, tries[i]);
|
||||||
|
bool is_bad_bad_ms = EndsWith(it, "AppData\\Local\\Microsoft\\WindowsApps") || EndsWith(it, "AppData/Local/Microsoft/WindowsApps");
|
||||||
|
if (FileExists(path_it) IF_OS_WINDOWS(&& !is_bad_bad_ms)) {
|
||||||
|
return Copy(allocator, path_it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetShell(Allocator allocator, String *exe, String *cmd, String in_cmd) {
|
||||||
|
#if OS_WINDOWS
|
||||||
|
*exe = "c:\\windows\\system32\\cmd.exe";
|
||||||
|
*cmd = Format(allocator, "%S /C %S", *exe, in_cmd);
|
||||||
|
#else
|
||||||
|
*exe = "/usr/bin/bash";
|
||||||
|
String temp_file = WriteTempFile(in_cmd); // @todo: maybe try to pass by stdio here
|
||||||
|
*cmd = Format(allocator, "%S %S", *exe, temp_file);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// WARNING: seems that this maybe can't work reliably?
|
// WARNING: seems that this maybe can't work reliably?
|
||||||
// in case of 'git grep a' it's possible that child process spawns it's own
|
// in case of 'git grep a' it's possible that child process spawns it's own
|
||||||
// child process and then it won't print anything because it won't have
|
// child process and then it won't print anything because it won't have
|
||||||
@@ -5,35 +458,39 @@
|
|||||||
// 'less' program which errors out and doesn't print anything
|
// 'less' program which errors out and doesn't print anything
|
||||||
// @todo: maybe I should ask someone smarter about this!
|
// @todo: maybe I should ask someone smarter about this!
|
||||||
void UpdateProcesses() {
|
void UpdateProcesses() {
|
||||||
ProfileFunction();
|
|
||||||
IterRemove(ActiveProcesses) {
|
IterRemove(ActiveProcesses) {
|
||||||
IterRemovePrepare(ActiveProcesses);
|
IterRemovePrepare(ActiveProcesses);
|
||||||
Scratch scratch;
|
Scratch scratch;
|
||||||
View *view = GetView({it.view_id});
|
View *view = GetView(it.args.output_view);
|
||||||
|
|
||||||
String poll = PollStdout(scratch, &it, false);
|
String poll = PollStdout(scratch, &it, false);
|
||||||
if (poll.len) {
|
if (poll.len) {
|
||||||
Append(view, poll, it.scroll_to_end);
|
Append(view, poll, it.args.scroll_to_end);
|
||||||
}
|
}
|
||||||
if (!IsValid(&it)) {
|
if (!IsValid(&it)) {
|
||||||
ReportDebugf("process %lld exit code = %d", it.id, it.exit_code);
|
ReportConsolef("process %lld exit code = %d", it.id, it.exit_code);
|
||||||
remove_item = true;
|
remove_item = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Exec(ViewID view, bool scroll_to_end, String cmd, String working_dir) {
|
ExecArgs ShellArgs(Allocator allocator, ViewID output_view, String cmd, String working_directory) {
|
||||||
Process process = SpawnProcess(cmd, working_dir, {}, ProcessEnviroment);
|
ExecArgs args = {};
|
||||||
ReportDebugf("process %lld start. is_valid = %d cmd = %S working_dir = %S", process.id, process.is_valid, cmd, working_dir);
|
SetShell(allocator, &args.exe, &args.cmd, cmd);
|
||||||
process.view_id = view.id;
|
args.env = ProcessEnviroment;
|
||||||
process.scroll_to_end = scroll_to_end;
|
args.cwd = working_directory;
|
||||||
if (process.is_valid) Add(&ActiveProcesses, process);
|
args.output_view = output_view;
|
||||||
|
args.poll_process = 1;
|
||||||
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Exec(ViewID view, bool scroll_to_end, String16 cmd16, String working_dir) {
|
Process Exec(ExecArgs args) {
|
||||||
Scratch scratch;
|
Process process = SpawnProcess(args);
|
||||||
String cmd = ToString(scratch, cmd16);
|
ReportConsolef("process %lld start. is_valid = %d cmd = %S working_dir = %S", process.id, process.is_valid, args.cmd, args.cwd);
|
||||||
Exec(view, scroll_to_end, cmd, working_dir);
|
if (process.is_valid && args.poll_process) {
|
||||||
|
Add(&ActiveProcesses, process);
|
||||||
|
}
|
||||||
|
return process;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ExecResult {
|
struct ExecResult {
|
||||||
@@ -42,10 +499,15 @@ struct ExecResult {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ExecResult ExecAndWait(Allocator allocator, String cmd, String working_dir, String stdin_string = {}) {
|
ExecResult ExecAndWait(Allocator allocator, String cmd, String working_dir, String stdin_string = {}) {
|
||||||
ReportDebugf("ExecAndWait cmd = %S working_dir = %S stdin_string = %S", cmd, working_dir, stdin_string);
|
ReportConsolef("ExecAndWait cmd = %S working_dir = %S stdin_string = %S", cmd, working_dir, stdin_string);
|
||||||
|
|
||||||
Buffer *scratch_buff = CreateScratchBuffer(allocator, 4096 * 4);
|
Buffer *scratch_buff = CreateScratchBuffer(allocator, 4096 * 4);
|
||||||
Process process = SpawnProcess(cmd, working_dir, stdin_string, ProcessEnviroment);
|
|
||||||
|
ExecArgs args = ShellArgs(allocator, {}, cmd, working_dir);
|
||||||
|
args.poll_process = 0;
|
||||||
|
args.open_stdin = 1;
|
||||||
|
Process process = Exec(args);
|
||||||
|
WriteStdin(&process, stdin_string);
|
||||||
|
CloseStdin(&process);
|
||||||
for (;IsValid(&process);) {
|
for (;IsValid(&process);) {
|
||||||
Scratch scratch(allocator);
|
Scratch scratch(allocator);
|
||||||
String poll = PollStdout(scratch, &process, true);
|
String poll = PollStdout(scratch, &process, true);
|
||||||
@@ -59,12 +521,12 @@ void KillProcess(View *view) {
|
|||||||
IterRemove(ActiveProcesses) {
|
IterRemove(ActiveProcesses) {
|
||||||
IterRemovePrepare(ActiveProcesses);
|
IterRemovePrepare(ActiveProcesses);
|
||||||
|
|
||||||
ViewID view_id = {it.view_id};
|
ViewID view_id = {it.args.output_view};
|
||||||
if (view_id == view->id) {
|
if (view_id == view->id) {
|
||||||
KillProcess(&it);
|
KillProcess(&it);
|
||||||
remove_item = true;
|
remove_item = true;
|
||||||
String string = "process was killed by user\n";
|
String string = "process was killed by user\n";
|
||||||
Append(view, string, it.scroll_to_end);
|
Append(view, string, it.args.scroll_to_end);
|
||||||
// dont break because that will fuck with removal ...
|
// dont break because that will fuck with removal ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +534,7 @@ void KillProcess(View *view) {
|
|||||||
|
|
||||||
bool ProcessIsActive(ViewID view) {
|
bool ProcessIsActive(ViewID view) {
|
||||||
For (ActiveProcesses) {
|
For (ActiveProcesses) {
|
||||||
if (it.view_id == view.id) {
|
if (it.args.output_view == view) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
#define PLUGIN_PROFILER 1
|
||||||
|
|
||||||
|
#include "plugin_profiler.h"
|
||||||
#include "basic/basic.h"
|
#include "basic/basic.h"
|
||||||
#include "basic/basic.cpp"
|
#include "basic/basic.cpp"
|
||||||
#include "profiler.h"
|
|
||||||
|
|
||||||
#include "SDL3/SDL.h"
|
#include "SDL3/SDL.h"
|
||||||
#include "external/glad/glad.c"
|
#include "external/glad/glad.c"
|
||||||
#include "external/glad/glad.h"
|
#include "external/glad/glad.h"
|
||||||
@@ -12,37 +13,70 @@
|
|||||||
#endif
|
#endif
|
||||||
#define MINICORO_IMPL
|
#define MINICORO_IMPL
|
||||||
#include "external/minicoro.h"
|
#include "external/minicoro.h"
|
||||||
|
|
||||||
#include "render/generated_font.cpp"
|
#include "render/generated_font.cpp"
|
||||||
#include "render/font.cpp"
|
#include "render/font.cpp"
|
||||||
#include "render/opengl.cpp"
|
#include "render/opengl.cpp"
|
||||||
|
|
||||||
#include "buffer.h"
|
#define PLUGIN_CONFIG 1
|
||||||
#include "view.h"
|
#define PLUGIN_SEARCH_WINDOW 1
|
||||||
#include "window.h"
|
#define PLUGIN_PROJECT_MANAGEMENT 1
|
||||||
#include "text_editor.h"
|
#define PLUGIN_WINDOW_MANAGEMENT 1
|
||||||
|
#define PLUGIN_DIRECTORY_NAVIGATION 1
|
||||||
|
#define PLUGIN_SEARCH_OPEN_BUFFERS 1
|
||||||
|
#define PLUGIN_PROJECT_MANAGEMENT 1
|
||||||
|
#define PLUGIN_BASIC_COMMANDS 1
|
||||||
|
#define PLUGIN_COMMAND_WINDOW 1
|
||||||
|
#define PLUGIN_SEARCH_WINDOW 1
|
||||||
|
#define PLUGIN_STATUS_WINDOW 1
|
||||||
|
#define PLUGIN_BUILD_WINDOW 1
|
||||||
|
#define PLUGIN_DEBUG_WINDOW 1
|
||||||
|
#define PLUGIN_RECORD_GC 1
|
||||||
|
#define PLUGIN_RECORD_EVENTS 1
|
||||||
|
#define PLUGIN_DIRECTORY_NAVIGATION 1
|
||||||
|
#define PLUGIN_LOAD_VCVARS OS_WINDOWS
|
||||||
|
#define PLUGIN_REMEDYBG OS_WINDOWS
|
||||||
|
#define PLUGIN_FILE_COMMANDS 1
|
||||||
|
#define PLUGIN_WORD_COMPLETE 1
|
||||||
|
|
||||||
|
#include "plugin_directory_navigation.h"
|
||||||
|
#include "plugin_search_window.h"
|
||||||
|
#include "plugin_project_management.h"
|
||||||
|
#include "plugin_config.h"
|
||||||
|
#include "text_editor.h"
|
||||||
#include "globals.cpp"
|
#include "globals.cpp"
|
||||||
#include "coroutines.cpp"
|
#include "coroutines.cpp"
|
||||||
|
#include "data_desc.cpp"
|
||||||
#include "buffer.cpp"
|
#include "buffer.cpp"
|
||||||
#include "view.cpp"
|
#include "view.cpp"
|
||||||
#include "window.cpp"
|
#include "window.cpp"
|
||||||
#include "window_command.cpp"
|
#include "fuzzy_search_view.cpp"
|
||||||
#include "window_debug.cpp"
|
|
||||||
#include "window_status.cpp"
|
|
||||||
#include "window_search.cpp"
|
|
||||||
#include "window_build.cpp"
|
|
||||||
|
|
||||||
#include "process.cpp"
|
#include "process.cpp"
|
||||||
#include "event.cpp"
|
#include "event.cpp"
|
||||||
#include "config.cpp"
|
#include "config.cpp"
|
||||||
|
#include "ui.cpp"
|
||||||
#include "commands.cpp"
|
#include "commands.cpp"
|
||||||
#include "commands_clipboard.cpp"
|
#include "commands_clipboard.cpp"
|
||||||
|
|
||||||
#include "scratch.cpp"
|
#include "scratch.cpp"
|
||||||
#include "draw.cpp"
|
#include "draw.cpp"
|
||||||
#include "test/tests.cpp"
|
#include "test/tests.cpp"
|
||||||
|
#include "plugin_config.cpp"
|
||||||
|
#include "plugin_window_management.cpp"
|
||||||
|
#include "plugin_directory_navigation.cpp"
|
||||||
|
#include "plugin_search_open_buffers.cpp"
|
||||||
|
#include "plugin_project_management.cpp"
|
||||||
|
#include "plugin_basic_commands.cpp"
|
||||||
|
#include "plugin_command_window.cpp"
|
||||||
|
#include "plugin_search_window.cpp"
|
||||||
|
#include "plugin_status_window.cpp"
|
||||||
|
#include "plugin_build_window.cpp"
|
||||||
|
#include "plugin_debug_window.cpp"
|
||||||
|
#include "plugin_record_gc.cpp"
|
||||||
|
#include "plugin_record_events.cpp"
|
||||||
|
#include "plugin_load_vcvars.cpp"
|
||||||
|
#include "plugin_remedybg.cpp"
|
||||||
|
#include "plugin_profiler.cpp"
|
||||||
|
#include "plugin_file_commands.cpp"
|
||||||
|
#include "plugin_word_complete.cpp"
|
||||||
|
|
||||||
#if OS_WASM
|
#if OS_WASM
|
||||||
EM_JS(void, JS_SetMouseCursor, (const char *cursor_str), {
|
EM_JS(void, JS_SetMouseCursor, (const char *cursor_str), {
|
||||||
@@ -127,54 +161,26 @@ void SetMouseCursor(Event event) {
|
|||||||
SetMouseCursor(SDL_SYSTEM_CURSOR_DEFAULT);
|
SetMouseCursor(SDL_SYSTEM_CURSOR_DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateScroll(Window *window, bool update_caret_scrolling) {
|
void CMD_QuitWithoutSaving() {
|
||||||
ProfileFunction();
|
#if PLUGIN_REMEDYBG
|
||||||
BSet set = GetBSet(window);
|
QuitDebugger();
|
||||||
|
#endif
|
||||||
|
AppIsRunning = false;
|
||||||
|
} RegisterCommand(CMD_QuitWithoutSaving, "", "Self explanatory");
|
||||||
|
|
||||||
// Scrolling with caret
|
void ShowQuitAppUI(mco_coro *co) {
|
||||||
if (update_caret_scrolling) {
|
UIAction res = ShowCloseAllUI(co);
|
||||||
Caret c = set.view->carets[0];
|
if (res != UIAction_Cancel) {
|
||||||
Int front = GetFront(c);
|
CMD_QuitWithoutSaving();
|
||||||
XY xy = PosToXY(set.buffer, front);
|
|
||||||
|
|
||||||
Rect2I visible = GetVisibleCells(window);
|
|
||||||
Vec2I visible_cells = GetSize(visible);
|
|
||||||
Vec2I visible_size = visible_cells * Vec2I{window->font->char_spacing, window->font->line_spacing};
|
|
||||||
Vec2I rect_size = GetSize(window->document_rect);
|
|
||||||
|
|
||||||
if (xy.line >= visible.max.y - 2) {
|
|
||||||
Int set_view_at_line = xy.line - (visible_cells.y - 1);
|
|
||||||
Int cut_off_y = Max((Int)0, visible_size.y - rect_size.y);
|
|
||||||
set.view->scroll.y = (set_view_at_line * window->font->line_spacing) + cut_off_y;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xy.line < visible.min.y + 1) {
|
|
||||||
set.view->scroll.y = xy.line * window->font->line_spacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xy.col >= visible.max.x - 1) {
|
|
||||||
Int set_view_at_line = xy.col - (visible_cells.x - 1);
|
|
||||||
Int cut_off_x = Max((Int)0, visible_size.x - rect_size.x);
|
|
||||||
set.view->scroll.x = (set_view_at_line * window->font->char_spacing) + cut_off_x;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xy.col <= visible.min.x) {
|
|
||||||
set.view->scroll.x = xy.col * window->font->char_spacing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clip scroll
|
|
||||||
{
|
|
||||||
Int last_line = LastLine(set.buffer);
|
|
||||||
set.view->scroll.y = Clamp(set.view->scroll.y, (Int)0, Max((Int)0, (last_line - 1) * window->font->line_spacing));
|
|
||||||
|
|
||||||
// @note:
|
|
||||||
// GetCharCountOfLongestLine is a bottleneck, there is probably an algorithm for
|
|
||||||
// calculating this value incrementally but do we even need X scrollbar or x clipping?
|
|
||||||
set.view->scroll.x = ClampBottom(set.view->scroll.x, (Int)0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CMD_Quit() {
|
||||||
|
RemoveCoroutine("ShowQuitAppUI");
|
||||||
|
CCtx *data = AddCoroutine(ShowQuitAppUI);
|
||||||
|
ResumeCoroutine(data);
|
||||||
|
} RegisterCommand(CMD_Quit, "", "Ask user which files he would like to save and exit");
|
||||||
|
|
||||||
void OnCommand(Event event) {
|
void OnCommand(Event event) {
|
||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
//
|
//
|
||||||
@@ -245,7 +251,7 @@ void OnCommand(Event event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Int p = ScreenSpaceToBufferPos(selected.window, selected.view, selected.buffer, mouse);
|
Int p = ScreenSpaceToBufferPos(selected.window, selected.view, selected.buffer, mouse);
|
||||||
Caret &caret = selected.view->carets[0];
|
Caret &caret = selected.view->carets[0];
|
||||||
caret = SetFrontWithAnchor(caret, DocumentAnchor, p);
|
caret = SetFrontWithAnchor(caret, DocumentAnchor, p);
|
||||||
}
|
}
|
||||||
@@ -291,7 +297,7 @@ void OnCommand(Event event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Ctrl() && Mouse(RIGHT)) {
|
if (event.ctrl && Mouse(RIGHT)) {
|
||||||
Vec2I mouse = MouseVec2I();
|
Vec2I mouse = MouseVec2I();
|
||||||
BSet active = GetBSet(ActiveWindowID);
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
bool mouse_in_document = AreOverlapping(mouse, active.window->document_rect);
|
bool mouse_in_document = AreOverlapping(mouse, active.window->document_rect);
|
||||||
@@ -336,7 +342,7 @@ void OnCommand(Event event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (Ctrl() && Mouse(LEFT)) {
|
if (event.ctrl && Mouse(LEFT)) {
|
||||||
MouseLoadWord(event);
|
MouseLoadWord(event);
|
||||||
} else if (Mouse(LEFT)) { // Uses Alt and shift
|
} else if (Mouse(LEFT)) { // Uses Alt and shift
|
||||||
Vec2I mouse = MouseVec2I();
|
Vec2I mouse = MouseVec2I();
|
||||||
@@ -351,11 +357,11 @@ void OnCommand(Event event) {
|
|||||||
DocumentSelected = active.window->id;
|
DocumentSelected = active.window->id;
|
||||||
|
|
||||||
Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse);
|
Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse);
|
||||||
if (Alt()) Insert(&active.view->carets, MakeCaret(p, p), 0);
|
if (event.alt) Insert(&active.view->carets, MakeCaret(p, p), 0);
|
||||||
if (!Alt() && !Shift()) active.view->carets.len = 1;
|
if (!event.alt && !event.shift) active.view->carets.len = 1;
|
||||||
|
|
||||||
Caret &caret = active.view->carets[0];
|
Caret &caret = active.view->carets[0];
|
||||||
if (Shift()) {
|
if (event.shift) {
|
||||||
if (p <= caret.range.min) {
|
if (p <= caret.range.min) {
|
||||||
caret.range.min = p;
|
caret.range.min = p;
|
||||||
caret.ifront = 0;
|
caret.ifront = 0;
|
||||||
@@ -364,7 +370,7 @@ void OnCommand(Event event) {
|
|||||||
caret.ifront = 1;
|
caret.ifront = 1;
|
||||||
}
|
}
|
||||||
} else if (event.clicks >= 2 && InBounds({caret.range.min - 1, caret.range.max + 1}, p)) {
|
} else if (event.clicks >= 2 && InBounds({caret.range.min - 1, caret.range.max + 1}, p)) {
|
||||||
Range range = EncloseLoadWord(active.buffer, p);
|
Range range = EncloseWord(active.buffer, p);
|
||||||
if (event.clicks >= 3) {
|
if (event.clicks >= 3) {
|
||||||
range = EncloseFullLine(active.buffer, p);
|
range = EncloseFullLine(active.buffer, p);
|
||||||
}
|
}
|
||||||
@@ -403,26 +409,22 @@ void OnCommand(Event event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BSet main = GetBSet(PrimaryWindowID);
|
|
||||||
BSet active = GetBSet(ActiveWindowID);
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
|
||||||
bool executed = false;
|
bool executed = false;
|
||||||
For (active.view->hooks) {
|
For (active.view->commands) {
|
||||||
if (it.trigger && MatchEvent(it.trigger, &event)) {
|
if (it.trigger && MatchEvent(it.trigger, &event)) {
|
||||||
ProfileScopeEx(it.name);
|
ProfileScopeEx(it.name);
|
||||||
it.function();
|
it.function();
|
||||||
LastExecutedManualCommand = it.function;
|
|
||||||
executed = true;
|
executed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (executed == false) {
|
if (executed == false) {
|
||||||
For (CommandFunctions) {
|
For (GlobalCommands) {
|
||||||
if (it.trigger && MatchEvent(it.trigger, &event)) {
|
if (it.trigger && MatchEvent(it.trigger, &event)) {
|
||||||
ProfileScopeEx(it.name);
|
ProfileScopeEx(it.name);
|
||||||
it.function();
|
it.function();
|
||||||
LastExecutedManualCommand = it.function;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -440,20 +442,39 @@ void OnCommand(Event event) {
|
|||||||
CMD_Quit();
|
CMD_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
IF_DEBUG(AssertRanges(main.view->carets));
|
if (event.kind == EVENT_KEY_PRESS && event.key == SDLK_ESCAPE && event.ctrl == false && event.shift == false && event.alt == false && event.super == false) {
|
||||||
IF_DEBUG(AssertRanges(active.view->carets));
|
if (active.window->lose_focus_on_escape && active.window->id == ActiveWindowID) {
|
||||||
|
if (active.window->primary) {
|
||||||
|
//
|
||||||
|
} else {
|
||||||
|
NextActiveWindowID = PrimaryWindowID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
For (Windows) {
|
||||||
|
if (it->lose_visibility_on_escape && it->visible) {
|
||||||
|
it->visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SLOW_BUILD
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
AssertRanges(main.view->carets);
|
||||||
|
AssertRanges(active.view->carets);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void EvalCommand(String command) {
|
void EvalCommand(String command) {
|
||||||
BSet active = GetBSet(ActiveWindowID);
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
For (active.view->hooks) {
|
For (active.view->commands) {
|
||||||
if (it.name == command) {
|
if (it.name == command) {
|
||||||
ProfileScopeEx(it.name);
|
ProfileScopeEx(it.name);
|
||||||
it.function();
|
it.function();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
For (CommandFunctions) {
|
For (GlobalCommands) {
|
||||||
if (it.name == command) {
|
if (it.name == command) {
|
||||||
ProfileScopeEx(it.name);
|
ProfileScopeEx(it.name);
|
||||||
it.function();
|
it.function();
|
||||||
@@ -477,18 +498,6 @@ void GarbageCollect() {
|
|||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
Allocator sys_allocator = GetSystemAllocator();
|
Allocator sys_allocator = GetSystemAllocator();
|
||||||
|
|
||||||
For(Buffers) {
|
|
||||||
if (it->file_mod_time) {
|
|
||||||
int64_t new_file_mod_time = GetFileModTime(it->name);
|
|
||||||
if (it->file_mod_time != new_file_mod_time) {
|
|
||||||
it->changed_on_disk = true;
|
|
||||||
if (it->dirty == false) {
|
|
||||||
ReopenBuffer(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IterRemove(Views) {
|
IterRemove(Views) {
|
||||||
IterRemovePrepare(Views);
|
IterRemovePrepare(Views);
|
||||||
|
|
||||||
@@ -506,7 +515,7 @@ void GarbageCollect() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ref = ViewIsReferenced(it->id);
|
bool ref = ViewIsReferenced(it);
|
||||||
if (ref) {
|
if (ref) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -516,9 +525,13 @@ void GarbageCollect() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if PLUGIN_RECORD_GC
|
||||||
RawAppendf(GCInfoBuffer, "View %d %S\n", (int)it->id.id, buffer ? buffer->name : String{"NULL"});
|
RawAppendf(GCInfoBuffer, "View %d %S\n", (int)it->id.id, buffer ? buffer->name : String{"NULL"});
|
||||||
remove_item = true;
|
#endif
|
||||||
|
Assert(it->special == false);
|
||||||
|
Dealloc(&it->commands);
|
||||||
Dealloc(it);
|
Dealloc(it);
|
||||||
|
remove_item = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
IterRemove(Buffers) {
|
IterRemove(Buffers) {
|
||||||
@@ -533,13 +546,16 @@ void GarbageCollect() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ref = BufferIsReferenced(it->id);
|
bool ref = BufferIsReferenced(it);
|
||||||
if (ref) {
|
if (ref) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if PLUGIN_RECORD_GC
|
||||||
RawAppendf(GCInfoBuffer, "Buff %d %S\n", (int)it->id.id, it->name);
|
RawAppendf(GCInfoBuffer, "Buff %d %S\n", (int)it->id.id, it->name);
|
||||||
|
#endif
|
||||||
|
Assert(it->special == false);
|
||||||
remove_item = true;
|
remove_item = true;
|
||||||
DeallocBuffer(it);
|
DeallocBuffer(it);
|
||||||
}
|
}
|
||||||
@@ -551,13 +567,16 @@ void GarbageCollect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (it->close) {
|
if (it->close) {
|
||||||
|
#if PLUGIN_RECORD_GC
|
||||||
RawAppendf(GCInfoBuffer, "Wind %d %d %d %d %d\n", (int)it->id.id, (int)it->total_rect.min.x, (int)it->total_rect.min.y, (int)it->total_rect.max.x, (int)it->total_rect.max.y);
|
RawAppendf(GCInfoBuffer, "Wind %d %d %d %d %d\n", (int)it->id.id, (int)it->total_rect.min.x, (int)it->total_rect.min.y, (int)it->total_rect.max.x, (int)it->total_rect.max.y);
|
||||||
|
#endif
|
||||||
Dealloc(&it->goto_history);
|
Dealloc(&it->goto_history);
|
||||||
Dealloc(&it->goto_redo);
|
Dealloc(&it->goto_redo);
|
||||||
|
Dealloc(&it->commands);
|
||||||
Dealloc(sys_allocator, it);
|
Dealloc(sys_allocator, it);
|
||||||
remove_item = true;
|
remove_item = true;
|
||||||
} else {
|
} else {
|
||||||
View *view = FindView(it->active_view, NULL);
|
View *view = GetView(it->active_view, NULL);
|
||||||
if (!view) {
|
if (!view) {
|
||||||
JumpToLastValidView(it);
|
JumpToLastValidView(it);
|
||||||
}
|
}
|
||||||
@@ -565,19 +584,70 @@ void GarbageCollect() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LayoutWindows(int16_t wx, int16_t wy) {
|
||||||
|
ProfileFunction();
|
||||||
|
Rect2I screen_rect = RectI0Size(wx, wy);
|
||||||
|
|
||||||
|
#if PLUGIN_STATUS_WINDOW
|
||||||
|
LayoutStatusWindow(&screen_rect, wx, wy);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_SEARCH_WINDOW
|
||||||
|
LayoutSearchWindow(&screen_rect, wx, wy);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_COMMAND_WINDOW
|
||||||
|
LayoutCommandWindow(&screen_rect, wx, wy);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_DEBUG_WINDOW
|
||||||
|
LayoutDebugWindow(&screen_rect, wx, wy);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_BUILD_WINDOW
|
||||||
|
LayoutBuildWindow(&screen_rect, wx, wy);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Column layout
|
||||||
|
Int c = 0;
|
||||||
|
double size = WindowCalcEvenResizerValue(wx, &c);
|
||||||
|
if (c == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
ForItem(n, Windows) {
|
||||||
|
if (!n->primary) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
n->total_rect = n->document_rect = CutLeft(&screen_rect, (Int)(size * n->weight));
|
||||||
|
if (i != (c - 1)) {
|
||||||
|
Int resizer_size = (Int)(PrimaryFont.char_spacing*0.5f);
|
||||||
|
n->resizer_rect = CutRight(&n->document_rect, resizer_size);
|
||||||
|
} else {
|
||||||
|
n->resizer_rect = {};
|
||||||
|
}
|
||||||
|
CalcNiceties(n);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Update(Event event) {
|
void Update(Event event) {
|
||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
LayoutWindows(event.xwindow, event.ywindow);
|
LayoutWindows(event.xwindow, event.ywindow);
|
||||||
|
|
||||||
Scratch scratch;
|
{
|
||||||
Array<Window *> order = GetWindowZOrder(scratch);
|
Scratch scratch;
|
||||||
For(order) {
|
Array<Window *> order = GetWindowZOrder(scratch);
|
||||||
if (!it->visible) {
|
For(order) {
|
||||||
continue;
|
if (!it->visible) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
View *view = GetView(it->active_view);
|
||||||
|
view->main_caret_on_begin_frame = view->carets[0];
|
||||||
|
view->update_scroll = true;
|
||||||
}
|
}
|
||||||
View *view = GetView(it->active_view);
|
|
||||||
view->main_caret_on_begin_frame = view->carets[0];
|
|
||||||
view->update_scroll = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
For (Windows) {
|
For (Windows) {
|
||||||
@@ -588,26 +658,37 @@ void Update(Event event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OnCommand(event);
|
OnCommand(event);
|
||||||
StatusWindowUpdate();
|
|
||||||
DebugWindowUpdate();
|
|
||||||
FuzzySearchViewUpdate();
|
|
||||||
SearchWindowUpdate();
|
|
||||||
UpdateProcesses();
|
|
||||||
CoUpdate(&event);
|
|
||||||
|
|
||||||
For(IterateInReverse(&order)) {
|
#if PLUGIN_DEBUG_WINDOW
|
||||||
|
UpdateDebugWindow();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_STATUS_WINDOW
|
||||||
|
UpdateStatusWindow();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_SEARCH_WINDOW
|
||||||
|
UpdateSearchWindow();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ActiveViewHook
|
||||||
|
{
|
||||||
|
BSet set = GetBSet(ActiveWindowID);
|
||||||
|
if (set.view->update_hook) {
|
||||||
|
set.view->update_hook();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateProcesses();
|
||||||
|
UpdateCoroutines(&event);
|
||||||
|
|
||||||
|
|
||||||
|
For (Windows) {
|
||||||
if (!it->visible) continue;
|
if (!it->visible) continue;
|
||||||
View *view = GetView(it->active_view);
|
View *view = GetView(it->active_view);
|
||||||
UpdateScroll(it, !AreEqual(view->main_caret_on_begin_frame, view->carets[0]) && view->update_scroll);
|
UpdateScroll(it, !AreEqual(view->main_caret_on_begin_frame, view->carets[0]) && view->update_scroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We update it here despite the name to make it sure that all the possible changes are
|
|
||||||
// included albeit with delayed response. If we did this at the beginning of the frame
|
|
||||||
// and the DebugWindowUpdated we wouldnt get to know that in the OnCommand.
|
|
||||||
For (Buffers) {
|
|
||||||
it->begin_frame_change_id = it->change_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
ProfileScope(UpdateWindows);
|
ProfileScope(UpdateWindows);
|
||||||
For (Windows) {
|
For (Windows) {
|
||||||
@@ -659,10 +740,21 @@ void Update(Event event) {
|
|||||||
|
|
||||||
// Behavior where these windows cannot be visible at the same time
|
// Behavior where these windows cannot be visible at the same time
|
||||||
{
|
{
|
||||||
WindowID id[] = {BuildWindowID, CommandWindowID, SearchWindowID};
|
WindowID id[] = {
|
||||||
for (int i = 0; i < Lengthof(id); i += 1) {
|
{-1}, // This is just so that we have a valid array if all windows are disabled
|
||||||
|
#if PLUGIN_BUILD_WINDOW
|
||||||
|
BuildWindowID,
|
||||||
|
#endif
|
||||||
|
#if PLUGIN_COMMAND_WINDOW
|
||||||
|
CommandWindowID,
|
||||||
|
#endif
|
||||||
|
#if PLUGIN_SEARCH_WINDOW
|
||||||
|
SearchWindowID,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
for (int i = 1; i < Lengthof(id); i += 1) {
|
||||||
if (ActiveWindowID == id[i]) {
|
if (ActiveWindowID == id[i]) {
|
||||||
for (int j = 0; j < Lengthof(id); j += 1) {
|
for (int j = 1; j < Lengthof(id); j += 1) {
|
||||||
if (i == j) continue;
|
if (i == j) continue;
|
||||||
Window *window = GetWindow(id[j]);
|
Window *window = GetWindow(id[j]);
|
||||||
window->visible = false;
|
window->visible = false;
|
||||||
@@ -679,48 +771,27 @@ void Update(Event event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IS THIS ENOUGH? Previously reopened everything
|
||||||
|
For (Windows) {
|
||||||
|
BSet set = GetBSet(it);
|
||||||
|
TryReopeningWhenModified(set.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
GarbageCollect();
|
GarbageCollect();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Windows_SetupVCVarsall(mco_coro *co) {
|
Array<Event> FrameEvents;
|
||||||
View *view = NULL;
|
|
||||||
{
|
|
||||||
Scratch scratch;
|
|
||||||
String working_dir = WorkDir;
|
|
||||||
String buffer_name = GetUniqueBufferName(working_dir, "vcvarsall-");
|
|
||||||
String cmd = Format(scratch, "\"%S\" && set", WindowsVCVarsPathToLoadDevEnviroment);
|
|
||||||
view = ExecHidden(buffer_name, cmd, working_dir);
|
|
||||||
}
|
|
||||||
for (;;) {
|
|
||||||
if (!ProcessIsActive(view->id)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
CoYield(co);
|
|
||||||
}
|
|
||||||
|
|
||||||
Scratch scratch;
|
|
||||||
Buffer *buffer = GetBuffer(view->active_buffer);
|
|
||||||
String16 string16 = GetString(buffer);
|
|
||||||
String string = ToString(scratch, string16);
|
|
||||||
Array<String> lines = Split(scratch, string, "\n");
|
|
||||||
For (lines) {
|
|
||||||
String s = Trim(it);
|
|
||||||
Array<String> values = Split(scratch, s, "=");
|
|
||||||
if (values.len == 1) continue;
|
|
||||||
Add(&ProcessEnviroment, Copy(GetSystemAllocator(), s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainLoop() {
|
void MainLoop() {
|
||||||
ProfileFunction();
|
ProfileFunction();
|
||||||
Scratch scratch;
|
|
||||||
FrameID += 1;
|
FrameID += 1;
|
||||||
Array<Event> frame_events = GetEventsForFrame(scratch);
|
FrameEvents.len = 0;
|
||||||
Serializer ser = {EventBuffer};
|
GetEventsForFrame(&FrameEvents);
|
||||||
For(frame_events) {
|
For (FrameEvents) {
|
||||||
if (it.kind != 1) {
|
#if PLUGIN_RECORD_EVENTS
|
||||||
if (!Testing) Serialize(&ser, &it);
|
if (it.kind != EVENT_UPDATE && !Testing) {
|
||||||
|
Serialize(EventBuffer, &it);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (it.xwindow == 0 || it.ywindow == 0) {
|
if (it.xwindow == 0 || it.ywindow == 0) {
|
||||||
int xwindow, ywindow;
|
int xwindow, ywindow;
|
||||||
@@ -749,6 +820,7 @@ void MainLoop() {
|
|||||||
// This shouldn't matter to the state of the program, only appearance for
|
// This shouldn't matter to the state of the program, only appearance for
|
||||||
// the user
|
// the user
|
||||||
{
|
{
|
||||||
|
Scratch scratch;
|
||||||
ProfileScope(SetWindowTitle);
|
ProfileScope(SetWindowTitle);
|
||||||
Window *window = GetActiveWind();
|
Window *window = GetActiveWind();
|
||||||
View *view = GetView(window->active_view);
|
View *view = GetView(window->active_view);
|
||||||
@@ -758,35 +830,68 @@ void MainLoop() {
|
|||||||
SDL_SetWindowTitle(SDLWindow, string.data);
|
SDL_SetWindowTitle(SDLWindow, string.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
Event *event = GetLast(frame_events);
|
Event *event = GetLast(FrameEvents);
|
||||||
SetMouseCursor(*event);
|
SetMouseCursor(*event);
|
||||||
LayoutWindows(event->xwindow, event->ywindow); // This is here to render changes in title bar size without a frame of delay
|
LayoutWindows(event->xwindow, event->ywindow); // This is here to render changes in title bar size without a frame of delay
|
||||||
BeginFrameRender(event->xwindow, event->ywindow);
|
BeginFrameRender(event->xwindow, event->ywindow);
|
||||||
|
|
||||||
Array<Window *> order = GetWindowZOrder(scratch);
|
{
|
||||||
For(IterateInReverse(&order)) {
|
Scratch scratch;
|
||||||
if (!it->visible) continue;
|
Array<Window *> order = GetWindowZOrder(scratch);
|
||||||
DrawWindow(it, *GetLast(frame_events));
|
For(IterateInReverse(&order)) {
|
||||||
|
if (!it->visible) continue;
|
||||||
|
DrawWindow(it, *GetLast(FrameEvents));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
EndFrameRender(event->xwindow, event->ywindow, BackgroundColor);
|
EndFrameRender(event->xwindow, event->ywindow, BackgroundColor);
|
||||||
SDL_GL_SwapWindow(SDLWindow);
|
SDL_GL_SwapWindow(SDLWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if _WIN32
|
#if OS_WINDOWS
|
||||||
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
|
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
|
||||||
#else
|
#else
|
||||||
extern char **environ;
|
extern char **environ;
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
InitScratch();
|
#if OS_WINDOWS
|
||||||
InitOS((OSErrorReport *)printf);
|
|
||||||
#if _WIN32
|
|
||||||
int argc = __argc;
|
int argc = __argc;
|
||||||
char **argv = __argv;
|
char **argv = __argv;
|
||||||
AttachConsole(ATTACH_PARENT_PROCESS);
|
AttachConsole(ATTACH_PARENT_PROCESS);
|
||||||
#endif
|
#endif
|
||||||
BeginProfiler();
|
InitScratch();
|
||||||
|
InitOS(ReportErrorf);
|
||||||
|
|
||||||
|
ProjectFolder = GetWorkingDir(Perm);
|
||||||
|
HomeFolder = SDL_GetUserFolder(SDL_FOLDER_HOME);
|
||||||
|
{
|
||||||
|
String sdl_config_path = SDL_GetPrefPath("krzosa", "text_editor");
|
||||||
|
if (sdl_config_path.len && sdl_config_path.data[sdl_config_path.len - 1] == '\\') {
|
||||||
|
sdl_config_path = Chop(sdl_config_path, 1); // chop '/'
|
||||||
|
}
|
||||||
|
if (sdl_config_path.len && sdl_config_path.data[sdl_config_path.len - 1] == '/') {
|
||||||
|
sdl_config_path = Chop(sdl_config_path, 1); // chop '/'
|
||||||
|
}
|
||||||
|
ConfigFolder = NormalizePath(Perm, sdl_config_path);
|
||||||
|
SDL_free(sdl_config_path.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if OS_WINDOWS
|
||||||
|
{
|
||||||
|
wchar_t *p = GetEnvironmentStringsW();
|
||||||
|
for (;p && p[0];) {
|
||||||
|
String16 string16((char16_t *)p);
|
||||||
|
String string = ToString(Perm, string16);
|
||||||
|
Add(&ProcessEnviroment, string);
|
||||||
|
p += string16.len + 1;
|
||||||
|
}
|
||||||
|
// FreeEnvironmentStringsW(p); // I get a trap here? why?
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
for (int i = 0; environ[i]; i += 1) {
|
||||||
|
Add(&ProcessEnviroment, Copy(Perm, environ[i]));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (1) {
|
if (1) {
|
||||||
RunArenaTest();
|
RunArenaTest();
|
||||||
@@ -798,25 +903,6 @@ int main(int argc, char **argv)
|
|||||||
// return 0;
|
// return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !OS_WINDOWS
|
|
||||||
for (int i = 0; environ[i]; i += 1) {
|
|
||||||
Add(&ProcessEnviroment, Copy(Perm, environ[i]));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
WorkDir = GetWorkingDir(Perm);
|
|
||||||
{
|
|
||||||
String sdl_config_path = SDL_GetPrefPath("krzosa", "text_editor");
|
|
||||||
if (sdl_config_path.len && sdl_config_path.data[sdl_config_path.len - 1] == '\\') {
|
|
||||||
sdl_config_path = Chop(sdl_config_path, 1); // chop '/'
|
|
||||||
}
|
|
||||||
if (sdl_config_path.len && sdl_config_path.data[sdl_config_path.len - 1] == '/') {
|
|
||||||
sdl_config_path = Chop(sdl_config_path, 1); // chop '/'
|
|
||||||
}
|
|
||||||
ConfigDir = NormalizePath(Perm, sdl_config_path);
|
|
||||||
SDL_free(sdl_config_path.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||||
ReportErrorf("Couldn't initialize SDL! %s", SDL_GetError());
|
ReportErrorf("Couldn't initialize SDL! %s", SDL_GetError());
|
||||||
return 1;
|
return 1;
|
||||||
@@ -840,16 +926,22 @@ int main(int argc, char **argv)
|
|||||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||||
|
|
||||||
SDL_DisplayID primary_display_id = SDL_GetPrimaryDisplay();
|
|
||||||
const SDL_DisplayMode *display_mode = SDL_GetCurrentDisplayMode(primary_display_id);
|
|
||||||
|
|
||||||
// int w8 = (int)(display_mode->w * 0.8);
|
// int w8 = (int)(display_mode->w * 0.8);
|
||||||
// int h8 = (int)(display_mode->h * 0.8);
|
// int h8 = (int)(display_mode->h * 0.8);
|
||||||
|
#if DEBUG_BUILD
|
||||||
|
int whalf = 1000;
|
||||||
|
int hhalf = 1000;
|
||||||
|
int xhalf = 100;
|
||||||
|
int yhalf = 100;
|
||||||
|
#else
|
||||||
|
SDL_DisplayID primary_display_id = SDL_GetPrimaryDisplay();
|
||||||
|
const SDL_DisplayMode *display_mode = SDL_GetCurrentDisplayMode(primary_display_id);
|
||||||
int whalf = (int)(display_mode->w * 0.5) - 10;
|
int whalf = (int)(display_mode->w * 0.5) - 10;
|
||||||
int hhalf = (int)(display_mode->h) - 120;
|
int hhalf = (int)(display_mode->h) - 120;
|
||||||
int xhalf = whalf;
|
int xhalf = whalf;
|
||||||
int yhalf = 30;
|
int yhalf = 30;
|
||||||
|
#endif
|
||||||
|
|
||||||
Uint32 window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
Uint32 window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||||
SDLWindow = SDL_CreateWindow("Text editor", whalf, hhalf, window_flags);
|
SDLWindow = SDL_CreateWindow("Text editor", whalf, hhalf, window_flags);
|
||||||
@@ -884,37 +976,98 @@ int main(int argc, char **argv)
|
|||||||
if (scale != 1.0f) DPIScale = scale;
|
if (scale != 1.0f) DPIScale = scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
InitBuffers();
|
// InitBuffers
|
||||||
|
{
|
||||||
|
Allocator sys_allocator = GetSystemAllocator();
|
||||||
|
Scratch scratch;
|
||||||
|
Buffer *null_buffer = CreateBuffer(sys_allocator, Format(scratch, "%S/scratch", ProjectFolder));
|
||||||
|
null_buffer->special = true;
|
||||||
|
View *null_view = CreateView(null_buffer->id);
|
||||||
|
null_view->special = true;
|
||||||
|
Assert(null_buffer->id == NullBufferID && null_view->id == NullViewID);
|
||||||
|
|
||||||
|
Buffer *logs_buffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectFolder, "logs", ""));
|
||||||
|
logs_buffer->special = true;
|
||||||
|
View *logs_view = CreateView(logs_buffer->id);
|
||||||
|
logs_view->special = true;
|
||||||
|
LogBuffer = logs_buffer;
|
||||||
|
LogView = logs_view;
|
||||||
|
|
||||||
|
#if PLUGIN_RECORD_GC
|
||||||
|
GCInfoBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectFolder, "gc"));
|
||||||
|
GCInfoBuffer->special = true;
|
||||||
|
GCInfoBuffer->no_history = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_RECORD_EVENTS
|
||||||
|
EventBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectFolder, "events"));
|
||||||
|
EventBuffer->no_history = true;
|
||||||
|
EventBuffer->special = true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
InitRender();
|
InitRender();
|
||||||
ReloadFont(PathToFont, (U32)FontSize);
|
ReloadFont(PathToFont, (U32)FontSize);
|
||||||
InitWindows();
|
CreateWind();
|
||||||
InitOS(ReportWarningf);
|
ReopenBuffer(GetBuffer(NullBufferID));
|
||||||
|
InitOS(ReportErrorf);
|
||||||
|
|
||||||
For (CommandFunctions) {
|
For (GlobalCommands) {
|
||||||
if (it.binding.len != 0) {
|
if (it.binding.len != 0) {
|
||||||
it.trigger = ParseKeyCached(it.binding);
|
it.trigger = ParseKeyCached(it.binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EnterKey = ParseKeyCached("enter");
|
||||||
|
EscapeKey = ParseKeyCached("escape");
|
||||||
|
OpenKeySet = ParseKeyCached("ctrl-q | enter | f12");
|
||||||
|
EnterOrEscapeKeySet = ParseKeyCached("escape | enter");
|
||||||
|
AltEnterKeySet = ParseKeyCached("alt-enter");
|
||||||
|
ShiftEnterKeySet = ParseKeyCached("shift-enter");
|
||||||
|
CheckKeybindingColission();
|
||||||
|
|
||||||
Scratch scratch;
|
|
||||||
GlobalConfigBufferID = LoadConfig(Format(scratch, "%S/config.te", GetExeDir(scratch)));
|
#if PLUGIN_CONFIG
|
||||||
|
{
|
||||||
|
Scratch scratch;
|
||||||
|
GlobalConfigBufferID = LoadConfig(Format(scratch, "%S/config.te", ConfigFolder));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
for (int i = 1; i < argc; i += 1) {
|
for (int i = 1; i < argc; i += 1) {
|
||||||
String it = argv[i];
|
String it = argv[i];
|
||||||
|
|
||||||
|
#if PLUGIN_CONFIG
|
||||||
if (EndsWith(it, ".te")) {
|
if (EndsWith(it, ".te")) {
|
||||||
LoadConfig(it);
|
LoadConfig(it);
|
||||||
} else {
|
|
||||||
Open(argv[i]);
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
Open(argv[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReportConsolef(":Set WorkDir '%S'", WorkDir);
|
|
||||||
if (Testing) InitTests();
|
if (Testing) InitTests();
|
||||||
#if OS_WINDOWS
|
#if PLUGIN_LOAD_VCVARS
|
||||||
CoRemove("Windows_SetupVCVarsall");
|
LoadVCVars();
|
||||||
CoData *co_data = CoAdd(Windows_SetupVCVarsall);
|
|
||||||
co_data->dont_wait_until_resolved = true;
|
|
||||||
CoResume(co_data);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_STATUS_WINDOW
|
||||||
|
InitStatusWindow();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_COMMAND_WINDOW
|
||||||
|
InitCommandWindow();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_DEBUG_WINDOW
|
||||||
|
InitDebugWindow();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_BUILD_WINDOW
|
||||||
|
InitBuildWindow();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PLUGIN_SEARCH_WINDOW
|
||||||
|
InitSearchWindow();
|
||||||
|
#endif
|
||||||
|
|
||||||
#if OS_WASM
|
#if OS_WASM
|
||||||
emscripten_set_main_loop(MainLoop, 0, 1);
|
emscripten_set_main_loop(MainLoop, 0, 1);
|
||||||
#else
|
#else
|
||||||
@@ -926,7 +1079,5 @@ int main(int argc, char **argv)
|
|||||||
SDL_DestroyWindow(SDLWindow);
|
SDL_DestroyWindow(SDLWindow);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
|
||||||
EndProfiler();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,217 @@
|
|||||||
|
struct Buffer;
|
||||||
|
struct View;
|
||||||
|
struct Window;
|
||||||
|
struct BufferID { Int id; Buffer *o; };
|
||||||
|
struct ViewID { Int id; View *o; };
|
||||||
|
struct WindowID { Int id; Window *o; };
|
||||||
|
union Range { struct { Int min; Int max; }; Int e[2]; };
|
||||||
|
struct Caret { union { Range range; Int pos[2]; }; Int ifront;};
|
||||||
|
union XY {
|
||||||
|
struct {Int col; Int line;};
|
||||||
|
struct {Int x; Int y; };
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void Function();
|
||||||
|
|
||||||
|
struct FunctionData {
|
||||||
|
String name;
|
||||||
|
Function *function;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Edit {
|
||||||
|
Range range;
|
||||||
|
String16 string;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HistoryEntry {
|
||||||
|
Array<Edit> edits;
|
||||||
|
Array<Caret> carets;
|
||||||
|
double time;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void CMDFunction();
|
||||||
|
struct Command {
|
||||||
|
String name;
|
||||||
|
String docs;
|
||||||
|
CMDFunction *function;
|
||||||
|
String binding; // DONT USE, only for ParseKeyCached
|
||||||
|
struct Trigger *trigger;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum UIAction {
|
||||||
|
UIAction_Null,
|
||||||
|
UIAction_Cancel,
|
||||||
|
UIAction_Yes,
|
||||||
|
UIAction_No,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExecArgs {
|
||||||
|
String exe;
|
||||||
|
String cmd;
|
||||||
|
String cwd;
|
||||||
|
Array<String> env;
|
||||||
|
|
||||||
|
ViewID output_view; // poll_process
|
||||||
|
struct {
|
||||||
|
U32 poll_process : 1;
|
||||||
|
U32 open_stdin : 1;
|
||||||
|
U32 scroll_to_end : 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Process {
|
||||||
|
bool is_valid;
|
||||||
|
int exit_code;
|
||||||
|
char platform[6 * 8];
|
||||||
|
Int id;
|
||||||
|
|
||||||
|
ExecArgs args; // memory for exe and all that is invalid
|
||||||
|
};
|
||||||
|
|
||||||
|
enum OpenKind {
|
||||||
|
OpenKind_Invalid,
|
||||||
|
OpenKind_Skip,
|
||||||
|
OpenKind_Exec,
|
||||||
|
OpenKind_Goto,
|
||||||
|
OpenKind_Command,
|
||||||
|
#if PLUGIN_CONFIG
|
||||||
|
OpenKind_Set,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef uint32_t ResolveOpenMeta;
|
||||||
|
enum {
|
||||||
|
ResolveOpenMeta_Normal = 0,
|
||||||
|
ResolveOpenMeta_DontError = 2,
|
||||||
|
ResolveOpenMeta_DontExec = 4, // dont error +
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ResolvedOpen {
|
||||||
|
OpenKind kind;
|
||||||
|
String path;
|
||||||
|
Int line, col;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
U32 existing_buffer : 1;
|
||||||
|
U32 exec_in_background : 1;
|
||||||
|
U32 use_python_shell : 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Buffer {
|
||||||
|
BufferID id;
|
||||||
|
String name;
|
||||||
|
Int change_id;
|
||||||
|
Int file_mod_time;
|
||||||
|
|
||||||
|
union {
|
||||||
|
U16 *data;
|
||||||
|
char16_t*str;
|
||||||
|
};
|
||||||
|
Int len;
|
||||||
|
Int cap;
|
||||||
|
Array<Int> line_starts;
|
||||||
|
|
||||||
|
Array<HistoryEntry> undo_stack;
|
||||||
|
Array<HistoryEntry> redo_stack;
|
||||||
|
int edit_phase;
|
||||||
|
Array<Command> commands;
|
||||||
|
struct {
|
||||||
|
uint32_t no_history : 1;
|
||||||
|
uint32_t no_line_starts : 1;
|
||||||
|
uint32_t dirty : 1;
|
||||||
|
uint32_t changed_on_disk : 1;
|
||||||
|
uint32_t temp : 1;
|
||||||
|
uint32_t dont_try_to_save_in_bulk_ops : 1;
|
||||||
|
uint32_t close : 1;
|
||||||
|
uint32_t special : 1;
|
||||||
|
#if PLUGIN_DIRECTORY_NAVIGATION
|
||||||
|
uint32_t is_dir : 1;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct View {
|
||||||
|
ViewID id;
|
||||||
|
BufferID active_buffer;
|
||||||
|
Vec2I scroll;
|
||||||
|
Array<Caret> carets;
|
||||||
|
|
||||||
|
// window | view
|
||||||
|
Caret main_caret_on_begin_frame;
|
||||||
|
bool update_scroll;
|
||||||
|
|
||||||
|
UIAction ui_action;
|
||||||
|
Array<Command> commands;
|
||||||
|
Function *update_hook;
|
||||||
|
uint64_t prev_search_line_hash;
|
||||||
|
struct {
|
||||||
|
uint32_t close : 1;
|
||||||
|
uint32_t special : 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GotoCrumb {
|
||||||
|
ViewID view_id;
|
||||||
|
Caret caret;
|
||||||
|
double time;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Window {
|
||||||
|
WindowID id;
|
||||||
|
ViewID active_view;
|
||||||
|
|
||||||
|
Rect2I total_rect;
|
||||||
|
Rect2I document_rect;
|
||||||
|
Rect2I scrollbar_rect;
|
||||||
|
Rect2I line_numbers_rect;
|
||||||
|
Rect2I resizer_rect;
|
||||||
|
|
||||||
|
Font *font;
|
||||||
|
double mouse_scroller_offset;
|
||||||
|
int z;
|
||||||
|
double weight;
|
||||||
|
Caret search_bar_anchor; // maybe move to view @todo
|
||||||
|
|
||||||
|
GotoCrumb begin_frame_crumb;
|
||||||
|
Array<GotoCrumb> goto_history;
|
||||||
|
Array<GotoCrumb> goto_redo;
|
||||||
|
|
||||||
|
ViewID active_goto_list;
|
||||||
|
Int goto_list_pos;
|
||||||
|
|
||||||
|
String ui_query_file;
|
||||||
|
void (*after_resolve_open_hook)(Window *window, ResolvedOpen *resolved);
|
||||||
|
Array<Command> commands;
|
||||||
|
struct {
|
||||||
|
uint32_t draw_scrollbar : 1;
|
||||||
|
uint32_t draw_line_numbers : 1;
|
||||||
|
uint32_t secondary_window_style : 1;
|
||||||
|
uint32_t draw_line_highlight : 1;
|
||||||
|
uint32_t visible : 1;
|
||||||
|
uint32_t primary : 1;
|
||||||
|
uint32_t close : 1;
|
||||||
|
uint32_t sync_visibility_with_focus : 1;
|
||||||
|
uint32_t lose_focus_on_escape : 1;
|
||||||
|
uint32_t lose_visibility_on_escape : 1;
|
||||||
|
uint32_t jump_history : 1;
|
||||||
|
uint32_t skip_checkpoint : 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Scroller {
|
||||||
|
Rect2 rect;
|
||||||
|
double begin;
|
||||||
|
double end;
|
||||||
|
Int line_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BSet {
|
||||||
|
struct Window *window;
|
||||||
|
View *view;
|
||||||
|
Buffer *buffer;
|
||||||
|
};
|
||||||
|
|
||||||
#define EVENT_KINDS \
|
#define EVENT_KINDS \
|
||||||
X(EVENT_NONE) \
|
X(EVENT_NONE) \
|
||||||
X(EVENT_UPDATE) \
|
X(EVENT_UPDATE) \
|
||||||
@@ -54,96 +268,10 @@ struct Event {
|
|||||||
#undef X
|
#undef X
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BSet {
|
struct FuzzyPair {
|
||||||
struct Window *window;
|
int32_t index;
|
||||||
View *view;
|
float rating;
|
||||||
Buffer *buffer;
|
|
||||||
};
|
};
|
||||||
BSet GetBSet(struct Window *window);
|
|
||||||
BSet GetBSet(WindowID window_id);
|
|
||||||
|
|
||||||
|
|
||||||
// @WARNING: be careful about using this, should only be used for debugging
|
|
||||||
// the problem with this is that we want events to be reproducible.
|
|
||||||
// We eat as many events as we can in a frame, we abstract the frame and so on.
|
|
||||||
// Dont use it
|
|
||||||
Int FrameID;
|
|
||||||
|
|
||||||
Allocator SysAllocator = {SystemAllocatorProc};
|
|
||||||
String ConfigDir;
|
|
||||||
float DPIScale = 1.0f;
|
|
||||||
|
|
||||||
void AfterEdit(View *view, Array<Edit> edits);
|
|
||||||
Scroller ComputeScrollerRect(Window *window);
|
|
||||||
void UpdateScroll(Window *window, bool update_caret_scrolling);
|
|
||||||
String GetMainDir();
|
|
||||||
|
|
||||||
void SelectEntireBuffer(View *view);
|
|
||||||
void Replace(View *view, String16 string);
|
|
||||||
void SelectRange(View *view, Range range);
|
|
||||||
void Append(View *view, String16 string, bool scroll_to_end_if_cursor_on_last_line);
|
|
||||||
void Append(View *view, String string, bool scroll_to_end_if_cursor_on_last_line);
|
|
||||||
Array<Edit> ReplaceEx(Allocator scratch, View *view, String16 string);
|
|
||||||
void Eval(String string);
|
|
||||||
void Eval(String16 string);
|
|
||||||
void ReportDebugf(const char *fmt, ...);
|
|
||||||
|
|
||||||
void ReplaceWithoutMovingCarets(Buffer *buffer, Range range, String16 string);
|
|
||||||
void ClipboardCopy(View *view);
|
|
||||||
void ClipboardPaste(View *view);
|
|
||||||
|
|
||||||
void ReportConsolef(const char *fmt, ...);
|
|
||||||
void ReportErrorf(const char *fmt, ...);
|
|
||||||
void ReportWarningf(const char *fmt, ...);
|
|
||||||
void Appendf(View *view, const char *fmt, ...);
|
|
||||||
|
|
||||||
Buffer *CreateBuffer(Allocator allocator, String name, Int size = 4096);
|
|
||||||
View *CreateView(BufferID active_buffer);
|
|
||||||
void ReopenBuffer(Buffer *buffer);
|
|
||||||
bool ProcessIsActive(ViewID view);
|
|
||||||
void EvalCommand(String command);
|
|
||||||
void EvalCommand(String16 command);
|
|
||||||
|
|
||||||
inline bool operator==(BufferID a, BufferID b) { return a.id == b.id; }
|
|
||||||
inline bool operator==(ViewID a, ViewID b) { return a.id == b.id; }
|
|
||||||
inline bool operator!=(BufferID a, BufferID b) { return a.id != b.id; }
|
|
||||||
inline bool operator!=(ViewID a, ViewID b) { return a.id != b.id; }
|
|
||||||
|
|
||||||
// This function as name suggests tries to open a buffer,
|
|
||||||
// there is no name resolution here, path should already be resolved etc.
|
|
||||||
//
|
|
||||||
// 1. It tries to find an already open buffer
|
|
||||||
// 2. Returns a buffer if the file doesn't exist (even if a directory doesn't exist)
|
|
||||||
// - This is a worry for later time, also we want to handle weird names and so on
|
|
||||||
// 3. If file exists we read it, convert to utf16, tabs to spaces etc.
|
|
||||||
//
|
|
||||||
// We don't really care about opening buffers that don't have proper paths
|
|
||||||
Buffer *BufferOpenFile(String path);
|
|
||||||
|
|
||||||
typedef void PFunction(void *param);
|
|
||||||
struct PFunctionData { String name; PFunction *function; };
|
|
||||||
|
|
||||||
struct Register_Function {
|
|
||||||
Register_Function(Array<FunctionData> *functions, String name, Function *f) {
|
|
||||||
int64_t pos = 0;
|
|
||||||
if (Seek(name, "_", &pos, 0)) {
|
|
||||||
name = Skip(name, pos + 1);
|
|
||||||
}
|
|
||||||
Add(functions, {name, f});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#define RegisterFunction(functions, name) Register_Function RF__##name(functions, #name, name)
|
|
||||||
|
|
||||||
struct Register_Command { Register_Command(Array<CommandData> *fucs, Function *function, String name, String binding, String doc = "") { if (StartsWith(name, "CMD_")) name = Skip(name, sizeof("CMD_") - 1); Add(fucs, {name, binding, function, doc}); } };
|
|
||||||
#define RegisterCommand(name, ...) Register_Command RC__##name(&CommandFunctions, name, #name, __VA_ARGS__)
|
|
||||||
|
|
||||||
const int DIR_RIGHT = 0;
|
|
||||||
const int DIR_LEFT = 1;
|
|
||||||
const int DIR_DOWN = 2;
|
|
||||||
const int DIR_UP = 3;
|
|
||||||
const int DIR_COUNT = 4;
|
|
||||||
const bool CTRL_PRESSED = true;
|
|
||||||
const bool SHIFT_PRESS = true;
|
|
||||||
|
|
||||||
enum VariableType {
|
enum VariableType {
|
||||||
VariableType_Invalid,
|
VariableType_Invalid,
|
||||||
@@ -174,37 +302,74 @@ struct Register_Variable {
|
|||||||
type name = __VA_ARGS__; \
|
type name = __VA_ARGS__; \
|
||||||
Register_Variable var_##name(&Variables, VariableType_##type, #name, &name)
|
Register_Variable var_##name(&Variables, VariableType_##type, #name, &name)
|
||||||
|
|
||||||
void AddHook(Array<CommandData> *arr, String name, String binding, Function *function);
|
void AddCommand(Array<Command> *arr, String name, struct Trigger *trigger, CMDFunction *function);
|
||||||
|
struct Register_Command {
|
||||||
|
Register_Command(Array<Command> *funcs, CMDFunction *function, String name, String binding, String docs = "") {
|
||||||
|
int64_t pos = 0;
|
||||||
|
if (Seek(name, "_", &pos, 0)) {
|
||||||
|
name = Skip(name, pos + 1);
|
||||||
|
}
|
||||||
|
Command cmd = {};
|
||||||
|
cmd.name = name;
|
||||||
|
cmd.binding = binding;
|
||||||
|
cmd.function = function;
|
||||||
|
cmd.docs = docs;
|
||||||
|
Reserve(funcs, 512);
|
||||||
|
Add(funcs, cmd);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#define RegisterCommand(name, binding, docs) Register_Command RC__##name(&GlobalCommands, name, #name, binding, docs)
|
||||||
|
#define RegisterCoroutineCommand(name, binding, docs, ...) void CMD_##name() {\
|
||||||
|
RemoveCoroutine(#name);\
|
||||||
|
CCtx *data = AddCoroutine(name);\
|
||||||
|
__VA_ARGS__\
|
||||||
|
ResumeCoroutine(data);\
|
||||||
|
}\
|
||||||
|
Register_Command RC__##name(&GlobalCommands, CMD_##name, #name, binding, docs)
|
||||||
|
|
||||||
enum OpenKind {
|
#define RegisterFunction(functions, name) Register_Function RF__##name(functions, #name, name)
|
||||||
OpenKind_Invalid,
|
struct Register_Function {
|
||||||
OpenKind_Skip,
|
Register_Function(Array<FunctionData> *functions, String name, Function *f) {
|
||||||
OpenKind_Exec,
|
int64_t pos = 0;
|
||||||
OpenKind_BackgroundExec,
|
if (Seek(name, "_", &pos, 0)) {
|
||||||
OpenKind_Goto,
|
name = Skip(name, pos + 1);
|
||||||
OpenKind_Command,
|
}
|
||||||
OpenKind_Set,
|
Add(functions, {name, f});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef uint32_t ResolveOpenMeta;
|
constexpr int DIR_RIGHT = 0;
|
||||||
enum {
|
constexpr int DIR_LEFT = 1;
|
||||||
ResolveOpenMeta_Normal = 0,
|
constexpr int DIR_DOWN = 2;
|
||||||
ResolveOpenMeta_DontError = 2,
|
constexpr int DIR_UP = 3;
|
||||||
ResolveOpenMeta_DontExec = 4, // dont error +
|
constexpr int DIR_COUNT = 4;
|
||||||
};
|
constexpr bool CTRL_PRESSED = true;
|
||||||
|
constexpr bool SHIFT_PRESS = true;
|
||||||
|
constexpr Int LAST_LINE = INT64_MAX;
|
||||||
|
|
||||||
struct ResolvedOpen {
|
BSet GetBSet(struct Window *window);
|
||||||
OpenKind kind;
|
BSet GetBSet(WindowID window_id);
|
||||||
String path;
|
void Append(View *view, String16 string, bool scroll_to_end_if_cursor_on_last_line);
|
||||||
Int line, col;
|
void Append(View *view, String string, bool scroll_to_end_if_cursor_on_last_line);
|
||||||
bool existing_buffer;
|
void ReplaceWithoutMovingCarets(Buffer *buffer, Range range, String16 string);
|
||||||
};
|
void ReportConsolef(const char *fmt, ...);
|
||||||
|
void ReportErrorf(const char *fmt, ...);
|
||||||
ResolvedOpen ResolveOpen(Allocator scratch, String path, ResolveOpenMeta meta);
|
void ReportWarningf(const char *fmt, ...);
|
||||||
|
void Appendf(View *view, const char *fmt, ...);
|
||||||
|
View *CreateView(BufferID active_buffer);
|
||||||
|
void EvalCommand(String command);
|
||||||
|
void EvalCommand(String16 command);
|
||||||
|
Rect2I GetVisibleCells(Window *window);
|
||||||
|
ResolvedOpen ResolveOpen(Allocator scratch, Window *window, String path, ResolveOpenMeta meta);
|
||||||
BSet Open(String path, ResolveOpenMeta meta = ResolveOpenMeta_Normal);
|
BSet Open(String path, ResolveOpenMeta meta = ResolveOpenMeta_Normal);
|
||||||
BSet Open(String16 path, ResolveOpenMeta meta = ResolveOpenMeta_Normal);
|
BSet Open(String16 path, ResolveOpenMeta meta = ResolveOpenMeta_Normal);
|
||||||
|
|
||||||
void CenterView(WindowID window);
|
void CenterView(WindowID window);
|
||||||
void TrimWhitespace(Buffer *buffer, bool trim_lines_with_caret = false);
|
void TrimWhitespace(Buffer *buffer, bool trim_lines_with_caret = false);
|
||||||
void ApplyFormattingTool(Buffer *buffer, String tool);
|
|
||||||
void JumpTempBuffer(BSet *set, String buffer_name = "");
|
void JumpTempBuffer(BSet *set, String buffer_name = "");
|
||||||
|
|
||||||
|
bool operator==(BufferID a, BufferID b) { return a.id == b.id; }
|
||||||
|
bool operator==(ViewID a, ViewID b) { return a.id == b.id; }
|
||||||
|
bool operator!=(BufferID a, BufferID b) { return a.id != b.id; }
|
||||||
|
bool operator!=(ViewID a, ViewID b) { return a.id != b.id; }
|
||||||
|
bool operator==(WindowID a, WindowID b) { return a.id == b.id; }
|
||||||
|
bool operator!=(WindowID a, WindowID b) { return a.id != b.id; }
|
||||||
|
|||||||
582
src/text_editor/ui.cpp
Normal file
582
src/text_editor/ui.cpp
Normal file
@@ -0,0 +1,582 @@
|
|||||||
|
void JumpTempBuffer(BSet *set, String buffer_name) {
|
||||||
|
if (buffer_name.len == 0) {
|
||||||
|
buffer_name = GetUniqueBufferName(GetDirectory(set->buffer), "temp");
|
||||||
|
}
|
||||||
|
set->view = WindowOpenBufferView(set->window, buffer_name);
|
||||||
|
set->buffer = GetBuffer(set->view->active_buffer);
|
||||||
|
set->buffer->temp = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddCommand(Array<Command> *arr, String name, Trigger *trigger, CMDFunction *function) {
|
||||||
|
IterRemove(*arr) {
|
||||||
|
IterRemovePrepare(*arr);
|
||||||
|
if (it.name == name) {
|
||||||
|
remove_item = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command cmd = {};
|
||||||
|
cmd.name = name;
|
||||||
|
cmd.function = function;
|
||||||
|
cmd.trigger = trigger;
|
||||||
|
cmd.docs = "Not listing commands attached to things anywhere currently, maybe should change?";
|
||||||
|
Add(arr, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define AddUIAction(VIEW,NAME,TRIGGER,ACTION) AddCommand(&((VIEW)->commands),(NAME),(TRIGGER),[](){BSet active = GetBSet(ActiveWindowID); active.view->ui_action = (ACTION);})
|
||||||
|
UIAction WaitForUIAction(mco_coro *co, BSet main) {
|
||||||
|
UIAction result = UIAction_Cancel;
|
||||||
|
for (;;) {
|
||||||
|
if (main.window->active_view != main.view->id || (main.window->id != PrimaryWindowID && main.window->id != ActiveWindowID) || main.window->close) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (main.view->ui_action != UIAction_Null) {
|
||||||
|
result = main.view->ui_action;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Yield(co);
|
||||||
|
}
|
||||||
|
Close(main.buffer->id);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIAction QueryUserYesNoCancel(mco_coro *co, BSet main, String question) {
|
||||||
|
JumpTempBuffer(&main);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
RawAppendf(main.buffer, R"==(
|
||||||
|
%S
|
||||||
|
|
||||||
|
:Yes :No :Cancel
|
||||||
|
)==", question);
|
||||||
|
main.view->carets[0] = FindNext(main.buffer, u":Yes", MakeCaret(0));
|
||||||
|
main.view->carets[0].range.min = main.view->carets[0].range.max;
|
||||||
|
AddUIAction(main.view, "Yes", EnterKey, UIAction_Yes);
|
||||||
|
AddUIAction(main.view, "No", NULL, UIAction_No);
|
||||||
|
AddUIAction(main.view, "Cancel", EscapeKey, UIAction_Cancel);
|
||||||
|
UIAction ui_action = WaitForUIAction(co, main);
|
||||||
|
return ui_action;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowUIMessagef(const char *fmt, ...) {
|
||||||
|
Scratch scratch;
|
||||||
|
STRING_FORMAT(scratch, fmt, string);
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
JumpTempBuffer(&main);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
RawAppendf(main.buffer, "\n %S\n :Close\n", string);
|
||||||
|
main.view->carets[0] = FindNext(main.buffer, u":Close", MakeCaret(0));
|
||||||
|
AddCommand(&main.view->commands, "Close", EnterOrEscapeKeySet, [](){
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
Close(active.buffer->id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DetectUserFileCallback(Window *window, ResolvedOpen *resolved) {
|
||||||
|
if (resolved->kind == OpenKind_Goto) {
|
||||||
|
if (IsFile(resolved->path)) {
|
||||||
|
resolved->kind = OpenKind_Skip;
|
||||||
|
window->ui_query_file = Intern(&GlobalInternTable, resolved->path);
|
||||||
|
window->after_resolve_open_hook = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String16 QueryUserString(mco_coro *co, String ask) {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
JumpTempBuffer(&main);
|
||||||
|
NextActiveWindowID = main.window->id;
|
||||||
|
RawAppendf(main.buffer, R"==(
|
||||||
|
(Enter) :Submit :Cancel (Escape)
|
||||||
|
|
||||||
|
%S
|
||||||
|
================================================
|
||||||
|
>)==", ask);
|
||||||
|
|
||||||
|
main.view->carets[0] = MakeCaret(GetBufferEnd(main.buffer));
|
||||||
|
AddUIAction(main.view, "Submit", EnterKey, UIAction_Yes);
|
||||||
|
AddUIAction(main.view, "Cancel", EscapeKey, UIAction_Cancel);
|
||||||
|
UIAction action = WaitForUIAction(co, main);
|
||||||
|
if (action != UIAction_Yes) {
|
||||||
|
return u"";
|
||||||
|
}
|
||||||
|
|
||||||
|
Caret a = FindNext(main.buffer, u">", MakeCaret(-1));
|
||||||
|
if (a.range.min == -1) {
|
||||||
|
ReportErrorf("You changed the format of the UI buffer, I was looking for the '>' but can't find it, can't proceed");
|
||||||
|
return u"";
|
||||||
|
}
|
||||||
|
|
||||||
|
a.range.min = a.range.max + 1;
|
||||||
|
a.range.max = GetBufferEnd(main.buffer);
|
||||||
|
return GetString(main.buffer, a.range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Considerations with coroutines:
|
||||||
|
// 1. Does scratch memory leak across Yield boundary? Or interacts badly with Yield stuff in any way?
|
||||||
|
// 2. Are pointers and globals correct over time? Or might they get deleted etc.
|
||||||
|
// 3. Imagine a scenario where the coroutine gets deleted before completion, will the memory leak?
|
||||||
|
UIAction ShowCloseAllUI(mco_coro *co) {
|
||||||
|
CCtx *ctx = GetCoroutineContext();
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
Array<BufferID> buffers = {ctx->arena};
|
||||||
|
For (Buffers) Add(&buffers, it->id);
|
||||||
|
ForItem (id, buffers) {
|
||||||
|
Buffer *it = GetBuffer(id, NULL);
|
||||||
|
if (it == NULL || it->special || !it->dirty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (it->temp || it->dont_try_to_save_in_bulk_ops) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String question = Format(ctx->arena, "Do you want to save [%S] before closing?", it->name);
|
||||||
|
UIAction ui_action = QueryUserYesNoCancel(co, main, question);
|
||||||
|
it = GetBuffer(id, NULL);
|
||||||
|
if (it && ui_action == UIAction_Yes) {
|
||||||
|
SaveBuffer(it);
|
||||||
|
} else if (ui_action == UIAction_No) {
|
||||||
|
} else if (ui_action == UIAction_Cancel) {
|
||||||
|
return UIAction_Cancel;
|
||||||
|
} ElseInvalidCodepath();
|
||||||
|
}
|
||||||
|
|
||||||
|
For(Buffers) {
|
||||||
|
Close(it->id);
|
||||||
|
}
|
||||||
|
return UIAction_Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
String QueryUserFile(mco_coro *co) {
|
||||||
|
#if PLUGIN_DIRECTORY_NAVIGATION
|
||||||
|
Window *window = GetWindow(PrimaryWindowID);
|
||||||
|
ViewID original_view = window->active_view;
|
||||||
|
Open(GetPrimaryDirectory());
|
||||||
|
BSet set = GetBSet(window);
|
||||||
|
RawReplaceText(set.buffer, {}, u"Point at a file you want to open!");
|
||||||
|
SelectRange(set.view, GetLineRangeWithoutNL(set.buffer, 0));
|
||||||
|
window->after_resolve_open_hook = DetectUserFileCallback;
|
||||||
|
String filename = "";
|
||||||
|
for (;;) {
|
||||||
|
BSet main = GetBSet(window);
|
||||||
|
if ((main.window->id != PrimaryWindowID && main.window->id != ActiveWindowID) || main.window->close) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window->after_resolve_open_hook == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Yield(co);
|
||||||
|
}
|
||||||
|
window->active_view = original_view;
|
||||||
|
return window->ui_query_file;
|
||||||
|
#else
|
||||||
|
String16 string16 = QueryUserString(co, "Please input the path to the desired file");
|
||||||
|
String result = "";
|
||||||
|
{
|
||||||
|
Scratch scratch;
|
||||||
|
String i = ToString(scratch, string16);
|
||||||
|
result = Intern(&GlobalInternTable, i);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CO_TestQueryFile(mco_coro *co) {
|
||||||
|
//String file = QueryUserFile(co);
|
||||||
|
String16 file16 = QueryUserString(co, "Input some kind of a string PLEASE!");
|
||||||
|
{
|
||||||
|
Scratch scratch;
|
||||||
|
String file = ToString(scratch, file16);
|
||||||
|
ReportConsolef("%S", file);
|
||||||
|
}
|
||||||
|
} RegisterCoroutineCommand(CO_TestQueryFile, "", "");
|
||||||
|
|
||||||
|
void MouseLoadWord(Event event, ResolveOpenMeta meta = ResolveOpenMeta_Normal) {
|
||||||
|
Vec2I mouse = MouseVec2I();
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
|
||||||
|
bool mouse_in_document = AreOverlapping(mouse, active.window->document_rect);
|
||||||
|
if (mouse_in_document) {
|
||||||
|
Int p = ScreenSpaceToBufferPosErrorOutOfBounds(active.window, active.view, active.buffer, mouse);
|
||||||
|
if (p != -1) {
|
||||||
|
Range enclose = EncloseLoadWord(active.buffer, p);
|
||||||
|
if (InBounds(active.view->carets[0].range, p)) enclose = active.view->carets[0].range;
|
||||||
|
String16 string = GetString(active.buffer, enclose);
|
||||||
|
|
||||||
|
active.view->carets.len = 1;
|
||||||
|
active.view->carets[0] = MakeCaret(p);
|
||||||
|
Open(string, meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsOpenBoundary(char c) {
|
||||||
|
bool result = c == 0 || c == ':' || c == '\t' || c == '\n' || c == '"' || c == '\'';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Variable *GetVariable(String name) {
|
||||||
|
Variable *var = NULL;
|
||||||
|
For (Variables) {
|
||||||
|
if (name == it.name) {
|
||||||
|
var = ⁢
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
|
||||||
|
String InsertVariables(Allocator allocator, String string) {
|
||||||
|
Scratch scratch(allocator);
|
||||||
|
Array<String> parts = {scratch};
|
||||||
|
String it = string;
|
||||||
|
for (;;) {
|
||||||
|
int64_t idx = 0;
|
||||||
|
bool found = Seek(it, "@", &idx, SeekFlag_None);
|
||||||
|
if (!found) {
|
||||||
|
if (it.len > 0) {
|
||||||
|
Add(&parts, it);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String prev = GetPrefix(it, idx);
|
||||||
|
if (prev.len > 0) {
|
||||||
|
Add(&parts, prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
it = Skip(it, idx + 1);
|
||||||
|
char c = At(it, 0);
|
||||||
|
String name = {};
|
||||||
|
if (c == '@') {
|
||||||
|
Add(&parts, String{"@", 1});
|
||||||
|
it = Skip(it, 1);
|
||||||
|
continue;
|
||||||
|
} else if (c == '(') {
|
||||||
|
char *start = it.data + 1;
|
||||||
|
while (At(it, 0) && At(it, 0) != ')') {
|
||||||
|
it = Skip(it, 1);
|
||||||
|
}
|
||||||
|
Int len = it.data - start;
|
||||||
|
name = {start, len};
|
||||||
|
it = Skip(it, 1); // skip ')'
|
||||||
|
} else {
|
||||||
|
char *start = it.data;
|
||||||
|
while (IsAlphanumeric(At(it, 0))) {
|
||||||
|
it = Skip(it, 1);
|
||||||
|
}
|
||||||
|
Int len = it.data - start;
|
||||||
|
name = {start, len};
|
||||||
|
}
|
||||||
|
Variable *variable = GetVariable(name);
|
||||||
|
if (!variable) {
|
||||||
|
ReportErrorf("Variable: %S, not found", name);
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
if (variable->type != VariableType_String) {
|
||||||
|
// @todo: this will not report - open will override
|
||||||
|
ReportErrorf("Variable: %S, not of type String", variable->type);
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(&parts, *variable->string);
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = Merge(allocator, parts, "");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestInsertVariable() {
|
||||||
|
Scratch scratch;
|
||||||
|
|
||||||
|
String a = "Thing/@(ProjectFolder)/Another";
|
||||||
|
String b = "Thing/@ProjectFolder/Another";
|
||||||
|
Assert(InsertVariables(scratch, a) == InsertVariables(scratch, b));
|
||||||
|
int c = 10;
|
||||||
|
|
||||||
|
} RegisterFunction(&TestFunctions, TestInsertVariable);
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Variables:
|
||||||
|
@ProjectFolder/build/te
|
||||||
|
@(ProjectFolder)/build/te
|
||||||
|
|
||||||
|
Start of string matchers:
|
||||||
|
! Execute with default shell
|
||||||
|
!! Execute hidden with default shell
|
||||||
|
:Command Execute editor command
|
||||||
|
:Set Special case for config stuff
|
||||||
|
http://
|
||||||
|
https://
|
||||||
|
commit
|
||||||
|
py: @todo, execute in python shell, this will use the python3 shell and pass the rest to it's stdin or maybe as a file
|
||||||
|
|
||||||
|
More comprehensive syntax for commands:
|
||||||
|
!{/usr/bin/python,Hidden,Input:Clipboard,Output:Clipboard,WorkingDirectory:@ProjectFolder,Env:{Thing:asd}}
|
||||||
|
|
||||||
|
Otherwise it does filepath parsing:
|
||||||
|
C:/windows/path
|
||||||
|
/unix/path
|
||||||
|
./it/also/does/linenums:32:3
|
||||||
|
/as/well/as/this/format(111,1)
|
||||||
|
./thing(10)
|
||||||
|
|
||||||
|
|
||||||
|
USECASE: Wouldn't it be cool to just select a part of codebase pipe that into a script
|
||||||
|
and get a result in a clipboard or capture the output and change the selection?
|
||||||
|
|
||||||
|
PREV IDEA:
|
||||||
|
!{bash,Out:Sel} SCRIPT
|
||||||
|
!{bash,Out:Clip} SCRIPT
|
||||||
|
Use variables for injecting selection: @Sel
|
||||||
|
|
||||||
|
TODO: I would pause the data desc language, seems a bit cumbersome... think of more concrete, constrained ideas
|
||||||
|
TODO: Unify lexers (Set and Trigger)
|
||||||
|
|
||||||
|
*/
|
||||||
|
ResolvedOpen ResolveOpen(Allocator alo, Window *window, String path, ResolveOpenMeta meta) {
|
||||||
|
ResolvedOpen result = {};
|
||||||
|
path = Trim(path);
|
||||||
|
bool exec = !(ResolveOpenMeta_DontExec & meta);
|
||||||
|
|
||||||
|
#if PLUGIN_CONFIG
|
||||||
|
path = InsertVariables(alo, path);
|
||||||
|
|
||||||
|
if (exec && result.kind == OpenKind_Invalid && StartsWith(path, ":Set ")) {
|
||||||
|
result.kind = OpenKind_Set;
|
||||||
|
result.path = Skip(path, 5);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// :Command
|
||||||
|
if (exec && result.kind == OpenKind_Invalid && StartsWith(path, ":")) {
|
||||||
|
result.kind = OpenKind_Command;
|
||||||
|
path = Skip(path, 1);
|
||||||
|
result.path.data = path.data;
|
||||||
|
for (Int i = 0; i < path.len; i += 1) {
|
||||||
|
if (IsNonWord(path.data[i])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result.path.len += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// !!exec_hidden
|
||||||
|
if (exec && result.kind == OpenKind_Invalid && StartsWith(path, "!!")) {
|
||||||
|
result.kind = OpenKind_Exec;
|
||||||
|
result.exec_in_background = 1;
|
||||||
|
result.path = Skip(path, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// !exec
|
||||||
|
if (exec && result.kind == OpenKind_Invalid && StartsWith(path, "!")) {
|
||||||
|
result.kind = OpenKind_Exec;
|
||||||
|
result.path = Skip(path, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exec && result.kind == OpenKind_Invalid && StartsWith(path, "py:")) {
|
||||||
|
result.kind = OpenKind_Exec;
|
||||||
|
result.path = Skip(path, 3);
|
||||||
|
result.use_python_shell = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://web
|
||||||
|
bool web = StartsWith(path, "https://") || StartsWith(path, "http://");
|
||||||
|
if (exec && result.kind == OpenKind_Invalid && web) {
|
||||||
|
result.path = Format(alo, "%S %S", InternetBrowser, path);
|
||||||
|
result.kind = OpenKind_Exec;
|
||||||
|
result.exec_in_background = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit 3kc09as92
|
||||||
|
if (exec && result.kind == OpenKind_Invalid && StartsWith(path, "commit ")) {
|
||||||
|
path = Skip(path, 7);
|
||||||
|
result.path = Format(alo, "git --no-pager show %S", path);
|
||||||
|
result.kind = OpenKind_Exec;
|
||||||
|
}
|
||||||
|
|
||||||
|
// c:/filename:32:12
|
||||||
|
if (result.kind == OpenKind_Invalid) {
|
||||||
|
String p = NormalizePath(alo, path);
|
||||||
|
if (p.len == 2 && IsAlphabetic(ToLowerCase(At(p, 0))) && At(p, 1) == ':') {
|
||||||
|
p = Format(alo, "%S/", p);
|
||||||
|
}
|
||||||
|
String pstart = p;
|
||||||
|
|
||||||
|
bool is_absolute = false;
|
||||||
|
if (IsAlphabetic(ToLowerCase(At(p, 0))) && At(p, 1) == ':' && At(p, 2) == '/') {
|
||||||
|
is_absolute = true;
|
||||||
|
p = Skip(p, 3);
|
||||||
|
} else if (At(p, 0) == '/') {
|
||||||
|
is_absolute = true;
|
||||||
|
p = Skip(p, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!IsOpenBoundary(At(p, 0))) {
|
||||||
|
p = Skip(p, 1);
|
||||||
|
}
|
||||||
|
path = {pstart.data, (Int)(p.data - pstart.data)};
|
||||||
|
|
||||||
|
// For (LINE:COLUMN): error: - we can either backtrack at the end since we are including
|
||||||
|
// the parenthesis and whitespace or alternatively we can look for patterns on every
|
||||||
|
// character move in the loop... For now let's do backtracking. This doesn't handle all paths
|
||||||
|
// but not sure if that's even what we want. ALL paths is hard.
|
||||||
|
{
|
||||||
|
Int i = path.len - 1;
|
||||||
|
if (At(path, i) == ')') {
|
||||||
|
i -= 1;
|
||||||
|
|
||||||
|
Int end = i;
|
||||||
|
while (IsDigit(At(path, i))) {
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
Int start = i;
|
||||||
|
String b = {path.data + 1 + start, (end - start)};
|
||||||
|
|
||||||
|
if (At(path, i) == '(') {
|
||||||
|
i -= 1;
|
||||||
|
path.len = i + 1;
|
||||||
|
result.line = strtoll(b.data, NULL, 10);
|
||||||
|
} else if (At(path, i) == ',') {
|
||||||
|
i -= 1;
|
||||||
|
|
||||||
|
end = i;
|
||||||
|
while (IsDigit(At(path, i))) {
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
start = i;
|
||||||
|
String a = {path.data + 1 + start, (end - start)};
|
||||||
|
|
||||||
|
if (At(path, i) == '(') {
|
||||||
|
i -= 1;
|
||||||
|
path.len = i + 1;
|
||||||
|
result.line = strtoll(a.data, NULL, 10);
|
||||||
|
result.col = strtoll(b.data, NULL, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.line == 0 && At(p, 0) == ':') {
|
||||||
|
p = Skip(p, 1);
|
||||||
|
result.line = SkipInt(&p);
|
||||||
|
if (At(p, 0) == ':') {
|
||||||
|
p = Skip(p, 1);
|
||||||
|
Int b = SkipInt(&p);
|
||||||
|
result.col = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer *existing_buffer = GetBuffer(path, NULL);
|
||||||
|
if (result.kind == OpenKind_Invalid && existing_buffer != NULL) {
|
||||||
|
result.path = path;
|
||||||
|
result.kind = OpenKind_Goto;
|
||||||
|
result.existing_buffer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.kind == OpenKind_Invalid && is_absolute && FileExists(path)) {
|
||||||
|
result.path = path;
|
||||||
|
result.kind = OpenKind_Goto;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.kind == OpenKind_Invalid) {
|
||||||
|
String rel_path = Format(alo, "%S/%S", GetDirectory(window), path);
|
||||||
|
existing_buffer = GetBuffer(rel_path, NULL);
|
||||||
|
if (existing_buffer || FileExists(rel_path)) {
|
||||||
|
if (existing_buffer) {
|
||||||
|
result.existing_buffer = 1;
|
||||||
|
}
|
||||||
|
result.path = rel_path;
|
||||||
|
result.kind = OpenKind_Goto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.kind == OpenKind_Invalid && (meta & ResolveOpenMeta_DontError)) {
|
||||||
|
result.path = path;
|
||||||
|
result.kind = OpenKind_Skip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window->after_resolve_open_hook) {
|
||||||
|
window->after_resolve_open_hook(window, &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
BSet Open(Window *window, String path, ResolveOpenMeta meta, bool set_active = true) {
|
||||||
|
Scratch scratch;
|
||||||
|
BSet set = GetBSet(window);
|
||||||
|
ResolvedOpen o = ResolveOpen(scratch, window, path, meta);
|
||||||
|
if (o.kind == OpenKind_Goto) {
|
||||||
|
if (set_active) {
|
||||||
|
NextActiveWindowID = set.window->id;
|
||||||
|
}
|
||||||
|
View *view = WindowOpenBufferView(set.window, o.path);
|
||||||
|
if (IsDir(o.path)) {
|
||||||
|
#if PLUGIN_DIRECTORY_NAVIGATION
|
||||||
|
OpenDirectoryNavigation(view);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
|
if (o.line != 0) {
|
||||||
|
if (o.col == 0) o.col = 1;
|
||||||
|
Int pos = XYToPos(buffer, {o.col - 1, o.line - 1});
|
||||||
|
SelectRange(view, MakeCaret(pos));
|
||||||
|
}
|
||||||
|
CenterView(window->id);
|
||||||
|
} else if (o.kind == OpenKind_Exec) {
|
||||||
|
ExecArgs args = {};// ShellArgs(scratch, LogView->id, o.path, GetPrimaryDirectory());
|
||||||
|
args.poll_process = 1;
|
||||||
|
args.output_view = LogView->id;
|
||||||
|
args.cwd = GetPrimaryDirectory();
|
||||||
|
if (o.exec_in_background == 0) {
|
||||||
|
if (set_active) {
|
||||||
|
NextActiveWindowID = set.window->id;
|
||||||
|
}
|
||||||
|
JumpTempBuffer(&set);
|
||||||
|
RawAppend(set.buffer, u"\n");
|
||||||
|
args.output_view = set.view->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (o.use_python_shell == 1) {
|
||||||
|
args.exe = FindPython(scratch);
|
||||||
|
String temp_file = WriteTempFile(o.path);
|
||||||
|
args.cmd = Format(scratch, "%S %S", args.exe, temp_file);
|
||||||
|
} else {
|
||||||
|
SetShell(scratch, &args.exe, &args.cmd, o.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Exec(args);
|
||||||
|
} else if (o.kind == OpenKind_Command) {
|
||||||
|
EvalCommand(o.path);
|
||||||
|
}
|
||||||
|
#if PLUGIN_CONFIG
|
||||||
|
else if (o.kind == OpenKind_Set) {
|
||||||
|
Set(o.path);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else if (o.kind == OpenKind_Skip) {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
ReportErrorf("Failed to open: %S", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetBSet(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
BSet Open(String path, ResolveOpenMeta meta) {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
main = Open(main.window, path, meta);
|
||||||
|
return main;
|
||||||
|
}
|
||||||
|
|
||||||
|
BSet Open(String16 path, ResolveOpenMeta meta) {
|
||||||
|
Scratch scratch;
|
||||||
|
String string = ToString(scratch, path);
|
||||||
|
return Open(string, meta);
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ API View *CreateView(BufferID active_buffer) {
|
|||||||
view->id = AllocViewID(view);
|
view->id = AllocViewID(view);
|
||||||
view->active_buffer = active_buffer;
|
view->active_buffer = active_buffer;
|
||||||
view->carets.allocator = al;
|
view->carets.allocator = al;
|
||||||
view->hooks.allocator = al;
|
view->commands.allocator = al;
|
||||||
Add(&view->carets, {0, 0});
|
Add(&view->carets, {0, 0});
|
||||||
Add(&Views, view);
|
Add(&Views, view);
|
||||||
return view;
|
return view;
|
||||||
@@ -16,30 +16,62 @@ API View *CreateView(BufferID active_buffer) {
|
|||||||
|
|
||||||
void Dealloc(View *view) {
|
void Dealloc(View *view) {
|
||||||
Dealloc(&view->carets);
|
Dealloc(&view->carets);
|
||||||
Dealloc(&view->hooks);
|
Dealloc(&view->commands);
|
||||||
Dealloc(view->carets.allocator, view);
|
Dealloc(view->carets.allocator, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
API View *FindView(ViewID view_id, View *default_view) {
|
View *GetView(ViewID id, View *default_view = Views[0]) {
|
||||||
For(Views) {
|
Int left = 0;
|
||||||
if (it->id == view_id) {
|
Int right = Views.len - 1;
|
||||||
return it;
|
View *result = default_view;
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
Int mid = left + (right - left) / 2;
|
||||||
|
View *it = Views[mid];
|
||||||
|
if (it->id == id) {
|
||||||
|
result = it;
|
||||||
|
break;
|
||||||
|
} else if (it->id.id < id.id) {
|
||||||
|
left = mid + 1;
|
||||||
|
} else {
|
||||||
|
right = mid - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return default_view;
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
API View *FindView(BufferID buffer_id, View *default_view) {
|
View *GetViewForBuffer(Buffer *buffer, bool *is_active = NULL) {
|
||||||
For(Views) {
|
View *view = NULL;
|
||||||
if (it->active_buffer == buffer_id) {
|
if (is_active) {
|
||||||
return it;
|
*is_active = false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return default_view;
|
|
||||||
|
BSet active = GetBSet(ActiveWindowID);
|
||||||
|
if (active.buffer->id == buffer->id) {
|
||||||
|
if (is_active) {
|
||||||
|
*is_active = true;
|
||||||
|
}
|
||||||
|
return active.view;
|
||||||
|
}
|
||||||
|
|
||||||
|
For(Views) {
|
||||||
|
if (it->active_buffer != buffer->id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
view = it;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!view) {
|
||||||
|
view = CreateView(buffer->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
API View *FindView(String name, View *default_view) {
|
API View *GetView(String name, View *default_view = Views[0]) {
|
||||||
For(Views) {
|
For (Views) {
|
||||||
Buffer *buffer = GetBuffer(it->active_buffer);
|
Buffer *buffer = GetBuffer(it->active_buffer);
|
||||||
if (buffer->name == name) {
|
if (buffer->name == name) {
|
||||||
return it;
|
return it;
|
||||||
@@ -48,8 +80,13 @@ API View *FindView(String name, View *default_view) {
|
|||||||
return default_view;
|
return default_view;
|
||||||
}
|
}
|
||||||
|
|
||||||
API View *GetView(ViewID id) {
|
API View *FindView(BufferID buffer_id, View *default_view) {
|
||||||
return FindView(id, Views[0]);
|
For (Views) {
|
||||||
|
if (it->active_buffer == buffer_id) {
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return default_view;
|
||||||
}
|
}
|
||||||
|
|
||||||
API View *OpenBufferView(String name) {
|
API View *OpenBufferView(String name) {
|
||||||
@@ -75,22 +112,34 @@ bool ViewIsActive(ViewID id) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
API bool ViewIsReferenced(ViewID view) {
|
API bool ViewIsReferenced(View *view) {
|
||||||
if (view == NullViewID) {
|
if (view->special) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ViewIsCrumb(view)) {
|
if (ViewIsCrumb(view->id)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ViewIsActive(view);
|
return ViewIsActive(view->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BufferIsReferenced(Buffer *buffer) {
|
||||||
|
if (buffer->special) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FindView(buffer->id, NULL)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Close(ViewID id) {
|
void Close(ViewID id) {
|
||||||
View *view = GetView(id);
|
View *view = GetView(id, NULL);
|
||||||
if (view) {
|
if (view) {
|
||||||
if (view->id.id == 0) {
|
if (view->special) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
view->close = true;
|
view->close = true;
|
||||||
@@ -109,65 +158,39 @@ String16 FetchLoadWord(View *view) {
|
|||||||
Buffer *buffer = GetBuffer(view->active_buffer);
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
Caret caret = view->carets[0];
|
Caret caret = view->carets[0];
|
||||||
Range range = caret.range;
|
Range range = caret.range;
|
||||||
if (GetSize(caret.range) == 0) range = EncloseLoadWord(buffer, GetFront(caret));
|
if (GetSize(caret.range) == 0) {
|
||||||
|
range = EncloseLoadWord(buffer, GetFront(caret));
|
||||||
|
}
|
||||||
String16 string = GetString(buffer, range);
|
String16 string = GetString(buffer, range);
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
String16 FetchFuzzyViewLoadLine(View *view) {
|
Array<Range> GetSelectedLinesSortedExclusive(Allocator allocator, View *view) {
|
||||||
Buffer *buffer = GetBuffer(view->active_buffer);
|
Scratch scratch(allocator);
|
||||||
Range range = view->carets[0].range;
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
String16 string = GetString(buffer, range);
|
Array<Caret> caret_copy = TightCopy(scratch, view->carets);
|
||||||
if (GetSize(range) == 0) {
|
Array<Caret> temp = TightCopy(scratch, view->carets);
|
||||||
Int line = PosToLine(buffer, range.min);
|
if (view->carets.len > 1) MergeSort(view->carets.len, caret_copy.data, temp.data);
|
||||||
if (line == 0) {
|
|
||||||
line = ClampTop(1ll, buffer->line_starts.len - 1ll);
|
|
||||||
}
|
|
||||||
string = GetLineStringWithoutNL(buffer, line);
|
|
||||||
|
|
||||||
Int idx = 0;
|
Array<Range> result = {allocator};
|
||||||
String16 delim = u"||>";
|
For(caret_copy) {
|
||||||
if (Seek(string, delim, &idx, SeekFlag_None)) {
|
Int min_line = PosToLine(buffer, it.range.min);
|
||||||
string = Skip(string, idx + delim.len);
|
Int max_line = PosToLine(buffer, it.range.max);
|
||||||
|
Range line_range = {min_line, max_line + 1};
|
||||||
|
|
||||||
|
if (result.len == 0) {
|
||||||
|
Add(&result, line_range);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Range *last = GetLast(result);
|
||||||
|
if (AreOverlapping(*last, line_range)) {
|
||||||
|
last->max = Max(last->max, line_range.max);
|
||||||
|
} else {
|
||||||
|
Add(&result, line_range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return string;
|
return result;
|
||||||
}
|
|
||||||
|
|
||||||
char16_t GetIndentChar() {
|
|
||||||
char16_t c = u' ';
|
|
||||||
if (IndentKindWhichIsTabsOrSpaces == "spaces") {
|
|
||||||
c = u' ';
|
|
||||||
} else if (IndentKindWhichIsTabsOrSpaces == "tabs") {
|
|
||||||
c = u'\t';
|
|
||||||
} else {
|
|
||||||
ReportErrorf("Invalid IndentKindWhichIsTabsOrSpaces value: %S", IndentKindWhichIsTabsOrSpaces);
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 GetIndentString(Allocator allocator, Int indent_size) {
|
|
||||||
char16_t *result = AllocArray(allocator, char16_t, indent_size + 1);
|
|
||||||
char16_t c = GetIndentChar();
|
|
||||||
|
|
||||||
for (int i = 0; i < indent_size; i += 1) {
|
|
||||||
result[i] = c;
|
|
||||||
}
|
|
||||||
result[indent_size] = 0;
|
|
||||||
String16 res = {result, indent_size};
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
String GetIndentString8(Allocator allocator, Int indent_size) {
|
|
||||||
char *result = AllocArray(allocator, char, indent_size + 1);
|
|
||||||
char c = (char)GetIndentChar();
|
|
||||||
|
|
||||||
for (int i = 0; i < indent_size; i += 1) {
|
|
||||||
result[i] = c;
|
|
||||||
}
|
|
||||||
result[indent_size] = 0;
|
|
||||||
String res = {result, indent_size};
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IndentedNewLine(View *view) {
|
void IndentedNewLine(View *view) {
|
||||||
@@ -178,24 +201,38 @@ void IndentedNewLine(View *view) {
|
|||||||
For(view->carets) {
|
For(view->carets) {
|
||||||
Int front = GetFront(it);
|
Int front = GetFront(it);
|
||||||
Int indent = GetLineIndent(buffer, PosToLine(buffer, front));
|
Int indent = GetLineIndent(buffer, PosToLine(buffer, front));
|
||||||
String indent_string = GetIndentString8(scratch, indent);
|
String string = Format(scratch, "\n%.*s", indent, " ");
|
||||||
String string = Format(scratch, "\n%S", indent_string);
|
|
||||||
String16 string16 = ToString16(scratch, string);
|
String16 string16 = ToString16(scratch, string);
|
||||||
AddEdit(&edits, it.range, string16);
|
AddEdit(&edits, it.range, string16);
|
||||||
}
|
}
|
||||||
EndEdit(buffer, &edits, &view->carets, KILL_SELECTION);
|
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
Caret FindPrev(Buffer *buffer, String16 needle, Caret caret) {
|
// WARNING: Don't use in user facing stuff
|
||||||
Int pos = GetMin(caret);
|
Caret BaseFindNext(Buffer *buffer, String16 needle, Caret caret, SeekFlag flag) {
|
||||||
String16 medium = GetString(buffer, {0, pos});
|
Int pos = GetMax(caret);
|
||||||
SeekFlag flag = SearchCaseSensitive ? SeekFlag_None : SeekFlag_IgnoreCase;
|
String16 medium = GetString(buffer, {pos, INT64_MAX});
|
||||||
if (SearchWordBoundary) {
|
|
||||||
flag |= SeekFlag_WordBoundary;
|
|
||||||
}
|
|
||||||
|
|
||||||
Caret result = caret;
|
Caret result = caret;
|
||||||
Int index = 0;
|
Int index = 0;
|
||||||
|
if (Seek(medium, needle, &index, flag)) {
|
||||||
|
result = MakeCaret(pos + index + needle.len, pos + index);
|
||||||
|
} else {
|
||||||
|
medium = GetString(buffer);
|
||||||
|
if (Seek(medium, needle, &index, flag)) {
|
||||||
|
result = MakeCaret(index + needle.len, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WARNING: Don't use in user facing stuff
|
||||||
|
Caret BaseFindPrev(Buffer *buffer, String16 needle, Caret caret, SeekFlag flag) {
|
||||||
|
Int pos = GetMin(caret);
|
||||||
|
String16 medium = GetString(buffer, {0, pos});
|
||||||
|
Caret result = caret;
|
||||||
|
Int index = 0;
|
||||||
if (Seek(medium, needle, &index, flag | SeekFlag_MatchFindLast)) {
|
if (Seek(medium, needle, &index, flag | SeekFlag_MatchFindLast)) {
|
||||||
result = MakeCaret(index, index + needle.len);
|
result = MakeCaret(index, index + needle.len);
|
||||||
} else {
|
} else {
|
||||||
@@ -208,26 +245,20 @@ Caret FindPrev(Buffer *buffer, String16 needle, Caret caret) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Caret FindNext(Buffer *buffer, String16 needle, Caret caret) {
|
Caret FindPrev(Buffer *buffer, String16 needle, Caret caret) {
|
||||||
Int pos = GetMax(caret);
|
|
||||||
String16 medium = GetString(buffer, {pos, INT64_MAX});
|
|
||||||
SeekFlag flag = SearchCaseSensitive ? SeekFlag_None : SeekFlag_IgnoreCase;
|
SeekFlag flag = SearchCaseSensitive ? SeekFlag_None : SeekFlag_IgnoreCase;
|
||||||
if (SearchWordBoundary) {
|
if (SearchWordBoundary) {
|
||||||
flag |= SeekFlag_WordBoundary;
|
flag |= SeekFlag_WordBoundary;
|
||||||
}
|
}
|
||||||
|
return BaseFindPrev(buffer, needle, caret, flag);
|
||||||
|
}
|
||||||
|
|
||||||
Caret result = caret;
|
Caret FindNext(Buffer *buffer, String16 needle, Caret caret) {
|
||||||
Int index = 0;
|
SeekFlag flag = SearchCaseSensitive ? SeekFlag_None : SeekFlag_IgnoreCase;
|
||||||
if (Seek(medium, needle, &index, flag)) {
|
if (SearchWordBoundary) {
|
||||||
result = MakeCaret(pos + index + needle.len, pos + index);
|
flag |= SeekFlag_WordBoundary;
|
||||||
} else {
|
|
||||||
medium = GetString(buffer);
|
|
||||||
if (Seek(medium, needle, &index, flag)) {
|
|
||||||
result = MakeCaret(index + needle.len, index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return BaseFindNext(buffer, needle, caret, flag);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FindAllEx(Array<Caret> *n, Buffer *buffer, String16 needle) {
|
void FindAllEx(Array<Caret> *n, Buffer *buffer, String16 needle) {
|
||||||
@@ -388,52 +419,91 @@ void MoveCarets(View *view, int direction, bool ctrl = false, bool shift = false
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MoveCaretsLine(View *view, int direction) {
|
void MoveCaretsLine(View *view, int direction) {
|
||||||
|
// This is so hard...
|
||||||
|
// It's because of the corner cases in the implementation, would be nice to simplify this somehow
|
||||||
|
// but don't know how. The multiple carets constraints + not every line ends with new line make it into
|
||||||
|
// really tricky code.
|
||||||
Assert(direction == DIR_DOWN || direction == DIR_UP);
|
Assert(direction == DIR_DOWN || direction == DIR_UP);
|
||||||
Scratch scratch;
|
Scratch scratch;
|
||||||
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
|
|
||||||
// @todo: this doesn't work well at the end of buffer
|
|
||||||
struct XYPair {
|
|
||||||
XY front;
|
|
||||||
XY back;
|
|
||||||
};
|
|
||||||
|
|
||||||
Buffer *buffer = GetBuffer(view->active_buffer);
|
// Save caret positions to fix them at end to the expected incremented by one positions
|
||||||
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
|
// :PreserveXYCarets (kind of)
|
||||||
MergeCarets(buffer, &view->carets);
|
struct XYPair { XY front; XY back; };
|
||||||
Array<XYPair> saved_xy = {scratch};
|
Array<XYPair> saved_xy = {scratch};
|
||||||
For(view->carets) {
|
For (view->carets) {
|
||||||
Int eof_current = 0;
|
|
||||||
Range lines_to_move_range = {GetFullLineStart(buffer, it.range.min), GetFullLineEnd(buffer, it.range.max, &eof_current)};
|
|
||||||
if (lines_to_move_range.min == 0 && direction == DIR_UP) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Int eof = 0;
|
|
||||||
Int next_line_start = lines_to_move_range.max;
|
|
||||||
Int next_line_end = GetFullLineEnd(buffer, next_line_start, &eof);
|
|
||||||
Int prev_line_end = lines_to_move_range.min - 1;
|
|
||||||
Int prev_line_start = GetFullLineStart(buffer, prev_line_end);
|
|
||||||
|
|
||||||
if (direction == DIR_DOWN && eof) {
|
|
||||||
continue;
|
|
||||||
} else if (direction == DIR_UP && eof_current) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 string = Copy16(scratch, GetString(buffer, lines_to_move_range));
|
|
||||||
|
|
||||||
AddEdit(&edits, lines_to_move_range, {});
|
|
||||||
if (direction == DIR_DOWN) {
|
|
||||||
AddEdit(&edits, MakeRange(next_line_end), string);
|
|
||||||
} else {
|
|
||||||
AddEdit(&edits, MakeRange(prev_line_start), string);
|
|
||||||
}
|
|
||||||
|
|
||||||
Add(&saved_xy, {PosToXY(buffer, GetFront(it)), PosToXY(buffer, GetBack(it))});
|
Add(&saved_xy, {PosToXY(buffer, GetFront(it)), PosToXY(buffer, GetBack(it))});
|
||||||
}
|
}
|
||||||
EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION);
|
|
||||||
|
|
||||||
Int line_offset = direction == DIR_UP ? -1 : +1;
|
Int line_offset = direction == DIR_UP ? -1 : +1;
|
||||||
|
int side_idx = direction == DIR_UP ? 0 : 1;
|
||||||
|
|
||||||
|
Array<Range> line_ranges = GetSelectedLinesSortedExclusive(scratch, view); // This is <min_line, max_line> not positions
|
||||||
|
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
|
||||||
|
MergeCarets(buffer, &view->carets);
|
||||||
|
ForItem (_lines, line_ranges) {
|
||||||
|
Range lines = {_lines.min, _lines.max - 1}; // inclusive
|
||||||
|
Int swap_line = lines.e[side_idx] + line_offset;
|
||||||
|
|
||||||
|
Range total_range = {};
|
||||||
|
{
|
||||||
|
Int total_min_line = 0;
|
||||||
|
Int total_max_line = 0;
|
||||||
|
if (direction == DIR_UP) {
|
||||||
|
total_min_line = swap_line;
|
||||||
|
total_max_line = lines.max;
|
||||||
|
if (total_min_line < 0) {
|
||||||
|
edits.len = 0;
|
||||||
|
saved_xy.len = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
total_min_line = lines.min;
|
||||||
|
total_max_line = swap_line;
|
||||||
|
if (total_max_line >= buffer->line_starts.len) {
|
||||||
|
edits.len = 0;
|
||||||
|
saved_xy.len = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Range arange = GetLineRange(buffer, total_min_line);
|
||||||
|
Range brange = GetLineRange(buffer, total_max_line);
|
||||||
|
total_range = {arange.min, brange.max};
|
||||||
|
}
|
||||||
|
|
||||||
|
String16 replacement_string = {};
|
||||||
|
{
|
||||||
|
Range selected_range_min = GetLineRange(buffer, lines.min);
|
||||||
|
Range selected_range_max = GetLineRange(buffer, lines.max);
|
||||||
|
Range swap_line_range = GetLineRange(buffer, swap_line);
|
||||||
|
String16 selected_string = GetString(buffer, {selected_range_min.min, selected_range_max.max});
|
||||||
|
String16 swap_string = GetString(buffer, swap_line_range);
|
||||||
|
if (direction == DIR_UP) {
|
||||||
|
bool ends_with_new_line = selected_string.len == 0 || (selected_string.len && selected_string[selected_string.len - 1] == u'\n');
|
||||||
|
bool doesnt_end_with_new_line_special_case = !ends_with_new_line;
|
||||||
|
if (doesnt_end_with_new_line_special_case) {
|
||||||
|
if (swap_string.len) swap_string.len -= 1;
|
||||||
|
selected_string = Concat(scratch, selected_string, u"\n");
|
||||||
|
}
|
||||||
|
replacement_string = Concat(scratch, selected_string, swap_string);
|
||||||
|
} else {
|
||||||
|
bool ends_with_new_line = swap_string.len == 0 || (swap_string.len && swap_string[swap_string.len - 1] == u'\n');
|
||||||
|
bool doesnt_end_with_new_line_special_case = !ends_with_new_line;
|
||||||
|
if (doesnt_end_with_new_line_special_case) {
|
||||||
|
if (selected_string.len) selected_string.len -= 1;
|
||||||
|
swap_string = Concat(scratch, swap_string, u"\n");
|
||||||
|
}
|
||||||
|
replacement_string = Concat(scratch, swap_string, selected_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddEdit(&edits, total_range, replacement_string);
|
||||||
|
}
|
||||||
|
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection, EndEdit_SkipFixingCaretsIWantToDoThatMyself);
|
||||||
|
|
||||||
for (Int i = 0; i < saved_xy.len; i += 1) {
|
for (Int i = 0; i < saved_xy.len; i += 1) {
|
||||||
Caret &caret = view->carets[i];
|
Caret &caret = view->carets[i];
|
||||||
XYPair &xypair = saved_xy[i];
|
XYPair &xypair = saved_xy[i];
|
||||||
@@ -441,9 +511,10 @@ void MoveCaretsLine(View *view, int direction) {
|
|||||||
xypair.back.line += line_offset;
|
xypair.back.line += line_offset;
|
||||||
Int front = XYToPos(buffer, xypair.front);
|
Int front = XYToPos(buffer, xypair.front);
|
||||||
Int back = XYToPos(buffer, xypair.back);
|
Int back = XYToPos(buffer, xypair.back);
|
||||||
|
|
||||||
caret = MakeCaret(front, back);
|
caret = MakeCaret(front, back);
|
||||||
}
|
}
|
||||||
|
MergeCarets(buffer, &view->carets);
|
||||||
|
IF_DEBUG(AssertRanges(view->carets));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateCursorVertical(View *view, int direction) {
|
void CreateCursorVertical(View *view, int direction) {
|
||||||
@@ -452,9 +523,9 @@ void CreateCursorVertical(View *view, int direction) {
|
|||||||
|
|
||||||
Int line_offset = direction == DIR_UP ? -1 : 1;
|
Int line_offset = direction == DIR_UP ? -1 : 1;
|
||||||
|
|
||||||
Scratch scratch;
|
Scratch scratch;
|
||||||
Array<Caret> arr = {scratch};
|
Array<Caret> arr = {scratch};
|
||||||
For(view->carets) {
|
For (view->carets) {
|
||||||
if (PosToLine(buffer, it.range.min) == PosToLine(buffer, it.range.max)) {
|
if (PosToLine(buffer, it.range.min) == PosToLine(buffer, it.range.max)) {
|
||||||
Int f = OffsetByLine(buffer, GetFront(it), line_offset);
|
Int f = OffsetByLine(buffer, GetFront(it), line_offset);
|
||||||
Int b = OffsetByLine(buffer, GetBack(it), line_offset);
|
Int b = OffsetByLine(buffer, GetBack(it), line_offset);
|
||||||
@@ -485,7 +556,7 @@ void Delete(View *view, int direction, bool ctrl = false) {
|
|||||||
// Delete indent in multiple of IndentSize
|
// Delete indent in multiple of IndentSize
|
||||||
Range indent_range = GetIndentRangeAtPos(buffer, it.range.min);
|
Range indent_range = GetIndentRangeAtPos(buffer, it.range.min);
|
||||||
if (ctrl == false && it.range.min > indent_range.min && it.range.max <= indent_range.max) {
|
if (ctrl == false && it.range.min > indent_range.min && it.range.max <= indent_range.max) {
|
||||||
Int offset = it.range.min - indent_range.min;
|
Int offset = it.range.min - indent_range.min;
|
||||||
Int to_delete = (offset % (IndentSize));
|
Int to_delete = (offset % (IndentSize));
|
||||||
if (to_delete == 0) to_delete = IndentSize;
|
if (to_delete == 0) to_delete = IndentSize;
|
||||||
to_delete = Clamp(to_delete, (Int)1, IndentSize);
|
to_delete = Clamp(to_delete, (Int)1, IndentSize);
|
||||||
@@ -503,7 +574,7 @@ void Delete(View *view, int direction, bool ctrl = false) {
|
|||||||
|
|
||||||
MergeCarets(buffer, &view->carets);
|
MergeCarets(buffer, &view->carets);
|
||||||
For(view->carets) AddEdit(&edits, it.range, {});
|
For(view->carets) AddEdit(&edits, it.range, {});
|
||||||
EndEdit(buffer, &edits, &view->carets, true);
|
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EncloseSpace(View *view) {
|
void EncloseSpace(View *view) {
|
||||||
@@ -514,34 +585,6 @@ void EncloseSpace(View *view) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Array<Range> GetSelectedLinesSorted(Allocator allocator, View *view) {
|
|
||||||
Scratch scratch(allocator);
|
|
||||||
Buffer *buffer = GetBuffer(view->active_buffer);
|
|
||||||
Array<Caret> caret_copy = TightCopy(scratch, view->carets);
|
|
||||||
Array<Caret> temp = TightCopy(scratch, view->carets);
|
|
||||||
if (view->carets.len > 1) MergeSort(view->carets.len, caret_copy.data, temp.data);
|
|
||||||
|
|
||||||
Array<Range> result = {allocator};
|
|
||||||
For(caret_copy) {
|
|
||||||
Int min_line = PosToLine(buffer, it.range.min);
|
|
||||||
Int max_line = PosToLine(buffer, it.range.max);
|
|
||||||
Range line_range = {min_line, max_line + 1};
|
|
||||||
|
|
||||||
if (result.len == 0) {
|
|
||||||
Add(&result, line_range);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Range *last = GetLast(result);
|
|
||||||
if (AreOverlapping(*last, line_range)) {
|
|
||||||
last->max = Max(last->max, line_range.max);
|
|
||||||
} else {
|
|
||||||
Add(&result, line_range);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IndentSelectedLines(View *view, bool shift = false) {
|
void IndentSelectedLines(View *view, bool shift = false) {
|
||||||
Scratch scratch;
|
Scratch scratch;
|
||||||
Buffer *buffer = GetBuffer(view->active_buffer);
|
Buffer *buffer = GetBuffer(view->active_buffer);
|
||||||
@@ -549,27 +592,59 @@ void IndentSelectedLines(View *view, bool shift = false) {
|
|||||||
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
|
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
|
||||||
MergeCarets(buffer, &view->carets);
|
MergeCarets(buffer, &view->carets);
|
||||||
|
|
||||||
char16_t indent_char = GetIndentChar();
|
// :PreserveXYCarets - maybe make it as one of the strategies of adjusting carets?
|
||||||
String16 indent_string = GetIndentString(scratch, IndentSize);
|
struct XYPair { XY front; XY back; };
|
||||||
Array<Range> line_ranges_to_indent = GetSelectedLinesSorted(scratch, view);
|
Array<XYPair> saved_xy = {scratch};
|
||||||
For(line_ranges_to_indent) {
|
For (view->carets) Add(&saved_xy, {PosToXY(buffer, GetFront(it)), PosToXY(buffer, GetBack(it))});
|
||||||
|
|
||||||
|
Array<Int> not_allowed_to_be_skipped_list = {scratch};
|
||||||
|
For (view->carets) {
|
||||||
|
if (GetSize(it.range) == 0) {
|
||||||
|
Add(¬_allowed_to_be_skipped_list, PosToLine(buffer, it.range.min));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Array<Range> line_ranges_to_indent = GetSelectedLinesSortedExclusive(scratch, view);
|
||||||
|
For (line_ranges_to_indent) {
|
||||||
for (Int i = it.min; i < it.max; i += 1) {
|
for (Int i = it.min; i < it.max; i += 1) {
|
||||||
Range pos_range_of_line = GetLineRange(buffer, i);
|
Range pos_range_of_line = GetLineRange(buffer, i);
|
||||||
|
String16 string = GetString(buffer, pos_range_of_line);
|
||||||
|
String16 without_whitespace = GetString(buffer, GetLineRangeWithoutNL(buffer, i));
|
||||||
|
if (without_whitespace.len == 0 && !Contains(not_allowed_to_be_skipped_list, i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String16 indent_string = u" ";
|
||||||
|
indent_string.len = IndentSize;
|
||||||
if (!shift) {
|
if (!shift) {
|
||||||
AddEdit(&edits, {pos_range_of_line.min, pos_range_of_line.min}, indent_string);
|
AddEdit(&edits, {pos_range_of_line.min, pos_range_of_line.min}, indent_string);
|
||||||
|
ForItem (xy, saved_xy) {
|
||||||
|
if (xy.front.y == i) xy.front.x += indent_string.len;
|
||||||
|
if (xy.back.y == i) xy.back.x += indent_string.len;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
String16 string = GetString(buffer, pos_range_of_line);
|
|
||||||
Int whitespace_len = 0;
|
Int whitespace_len = 0;
|
||||||
for (Int i = 0; i < IndentSize && i < string.len && string.data[i] == indent_char; i += 1) {
|
for (Int ii = 0; ii < IndentSize && ii < string.len && string.data[ii] == u' '; ii += 1) {
|
||||||
whitespace_len += 1;
|
whitespace_len += 1;
|
||||||
}
|
}
|
||||||
|
ForItem (xy, saved_xy) {
|
||||||
|
if (xy.front.y == i) xy.front.x -= whitespace_len;
|
||||||
|
if (xy.back.y == i) xy.back.x -= whitespace_len;
|
||||||
|
}
|
||||||
|
|
||||||
AddEdit(&edits, {pos_range_of_line.min, pos_range_of_line.min + whitespace_len}, u"");
|
AddEdit(&edits, {pos_range_of_line.min, pos_range_of_line.min + whitespace_len}, u"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION);
|
EndEdit(buffer, &edits, &view->carets, !EndEdit_KillSelection);
|
||||||
|
|
||||||
|
for (Int i = 0; i < saved_xy.len; i += 1) {
|
||||||
|
Caret &caret = view->carets[i];
|
||||||
|
XYPair &xypair = saved_xy[i];
|
||||||
|
Int front = XYToPos(buffer, xypair.front);
|
||||||
|
Int back = XYToPos(buffer, xypair.back);
|
||||||
|
caret = MakeCaret(front, back);
|
||||||
|
}
|
||||||
view->update_scroll = false;
|
view->update_scroll = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,9 +681,9 @@ Array<Edit> ReplaceEx(Allocator scratch, View *view, String16 string) {
|
|||||||
For(view->carets) {
|
For(view->carets) {
|
||||||
AddEdit(&edits, it.range, string);
|
AddEdit(&edits, it.range, string);
|
||||||
}
|
}
|
||||||
EndEdit(buffer, &edits, &view->carets, KILL_SELECTION);
|
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
|
||||||
return edits;
|
return edits;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Replace(View *view, String16 string) {
|
void Replace(View *view, String16 string) {
|
||||||
Scratch scratch;
|
Scratch scratch;
|
||||||
@@ -635,8 +710,9 @@ void DuplicateLine(View *view, int direction) {
|
|||||||
Int pos = direction == DIR_UP ? range.min : range.max;
|
Int pos = direction == DIR_UP ? range.min : range.max;
|
||||||
AddEdit(&edits, MakeRange(pos), string);
|
AddEdit(&edits, MakeRange(pos), string);
|
||||||
}
|
}
|
||||||
EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION);
|
EndEdit(buffer, &edits, &view->carets, !EndEdit_KillSelection);
|
||||||
|
|
||||||
|
// Move carets now in the duplicate direction
|
||||||
Int coef = direction == DIR_UP ? -1 : 1;
|
Int coef = direction == DIR_UP ? -1 : 1;
|
||||||
for (Int i = 0; i < edits.len; i += 1) {
|
for (Int i = 0; i < edits.len; i += 1) {
|
||||||
Caret *caret = view->carets.data + i;
|
Caret *caret = view->carets.data + i;
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
struct View;
|
|
||||||
struct ViewID { Int id; View *o; };
|
|
||||||
|
|
||||||
typedef void Function();
|
|
||||||
struct FunctionData {
|
|
||||||
String name;
|
|
||||||
Function *function;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CommandData {
|
|
||||||
String name;
|
|
||||||
String binding;
|
|
||||||
Function *function;
|
|
||||||
String doc;
|
|
||||||
struct Trigger *trigger;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ViewKind {
|
|
||||||
ViewKind_Normal,
|
|
||||||
ViewKind_FuzzySearch,
|
|
||||||
ViewKind_ActiveSearch,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct View {
|
|
||||||
ViewID id;
|
|
||||||
BufferID active_buffer;
|
|
||||||
ViewKind kind;
|
|
||||||
Vec2I scroll;
|
|
||||||
Array<Caret> carets;
|
|
||||||
|
|
||||||
// window | view
|
|
||||||
Caret main_caret_on_begin_frame;
|
|
||||||
bool update_scroll;
|
|
||||||
|
|
||||||
String hook_cmd;
|
|
||||||
Array<CommandData> hooks;
|
|
||||||
uint64_t prev_search_line_hash;
|
|
||||||
struct {
|
|
||||||
unsigned close : 1;
|
|
||||||
unsigned special : 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GotoCrumb {
|
|
||||||
ViewID view_id;
|
|
||||||
Caret caret;
|
|
||||||
double time;
|
|
||||||
};
|
|
||||||
|
|
||||||
API ViewID AllocViewID(View *view);
|
|
||||||
API View *CreateView(BufferID active_buffer);
|
|
||||||
API View *FindView(ViewID view_id, View *default_view = NULL);
|
|
||||||
API View *FindView(BufferID buffer_id, View *default_view = NULL);
|
|
||||||
API View *FindView(String name, View *default_view = NULL);
|
|
||||||
API View *GetView(ViewID id);
|
|
||||||
API View *OpenBufferView(String name);
|
|
||||||
API bool ViewIsCrumb(ViewID view_id);
|
|
||||||
API bool ViewIsReferenced(ViewID view);
|
|
||||||
@@ -45,19 +45,24 @@ Window *FindWindow(ViewID view_id, Window *default_window = NULL) {
|
|||||||
|
|
||||||
Window *FindWindow(String buffer_name, Window *default_window = NULL) {
|
Window *FindWindow(String buffer_name, Window *default_window = NULL) {
|
||||||
For(Windows) {
|
For(Windows) {
|
||||||
View *it_view = GetView(it->active_view);
|
View *it_view = GetView(it->active_view, NULL);
|
||||||
Buffer *it_buffer = GetBuffer(it_view->active_buffer);
|
if (it_view) {
|
||||||
if (it_buffer->name == buffer_name) {
|
Buffer *it_buffer = GetBuffer(it_view->active_buffer, NULL);
|
||||||
return it;
|
Assert(it_buffer); // Probably we are missing something when GCing / closing
|
||||||
|
if (it_buffer && it_buffer->name == buffer_name) {
|
||||||
|
return it;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return default_window;
|
return default_window;
|
||||||
}
|
}
|
||||||
|
|
||||||
Window *FindWindow(BufferID buffer_id) {
|
Window *FindWindow(BufferID buffer_id) {
|
||||||
For(Windows) {
|
For (Windows) {
|
||||||
View *view = GetView(it->active_view);
|
View *view = GetView(it->active_view, Views[0]);
|
||||||
if (view->active_buffer == buffer_id) return it;
|
if (view->active_buffer == buffer_id) {
|
||||||
|
return it;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -92,7 +97,7 @@ Int GetExpandingBarSize(Window *window) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
View *WindowOpenBufferView(Window *new_parent_window, String name) {
|
View *WindowOpenBufferView(Window *new_parent_window, String name) {
|
||||||
View *view = FindView(name);
|
View *view = GetView(name, NULL);
|
||||||
if (!view) {
|
if (!view) {
|
||||||
View *result = OpenBufferView(name);
|
View *result = OpenBufferView(name);
|
||||||
new_parent_window->active_view = result->id;
|
new_parent_window->active_view = result->id;
|
||||||
@@ -114,15 +119,6 @@ View *WindowOpenBufferView(Window *new_parent_window, String name) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitWindows() {
|
|
||||||
CreateWind();
|
|
||||||
CommandWindowInit();
|
|
||||||
StatusWindowInit();
|
|
||||||
DebugWindowInit();
|
|
||||||
SearchWindowInit();
|
|
||||||
BuildWindowInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CalcNiceties(Window *n) {
|
void CalcNiceties(Window *n) {
|
||||||
float scrollbar_size = (10.f * DPIScale);
|
float scrollbar_size = (10.f * DPIScale);
|
||||||
float line_numbers_size = (float)n->font->char_spacing * 10.f;
|
float line_numbers_size = (float)n->font->char_spacing * 10.f;
|
||||||
@@ -143,41 +139,6 @@ double WindowCalcEvenResizerValue(Int screen_size_x, Int *out_count = NULL) {
|
|||||||
return (double)screen_size_x / w;
|
return (double)screen_size_x / w;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LayoutWindows(int16_t wx, int16_t wy) {
|
|
||||||
ProfileFunction();
|
|
||||||
Rect2I screen_rect = RectI0Size(wx, wy);
|
|
||||||
|
|
||||||
StatusWindowLayout(&screen_rect, wx, wy);
|
|
||||||
CommandWindowLayout(&screen_rect, wx, wy);
|
|
||||||
DebugWindowLayout(&screen_rect, wx, wy);
|
|
||||||
SearchWindowLayout(&screen_rect, wx, wy);
|
|
||||||
BuildWindowLayout(&screen_rect, wx, wy);
|
|
||||||
|
|
||||||
// Column layout
|
|
||||||
Int c = 0;
|
|
||||||
double size = WindowCalcEvenResizerValue(wx, &c);
|
|
||||||
if (c == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
ForItem(n, Windows) {
|
|
||||||
if (!n->primary) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
n->total_rect = n->document_rect = CutLeft(&screen_rect, (Int)(size * n->weight));
|
|
||||||
if (i != (c - 1)) {
|
|
||||||
Int resizer_size = (Int)(PrimaryFont.char_spacing*0.5f);
|
|
||||||
n->resizer_rect = CutRight(&n->document_rect, resizer_size);
|
|
||||||
} else {
|
|
||||||
n->resizer_rect = {};
|
|
||||||
}
|
|
||||||
CalcNiceties(n);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Window *GetOverlappingWindow(Vec2I p, Window *default_window = NULL) {
|
Window *GetOverlappingWindow(Vec2I p, Window *default_window = NULL) {
|
||||||
For(Windows) {
|
For(Windows) {
|
||||||
if (AreOverlapping(p, it->total_rect)) {
|
if (AreOverlapping(p, it->total_rect)) {
|
||||||
@@ -236,7 +197,7 @@ Int ScreenSpaceToBufferPosErrorOutOfBounds(Window *window, View *view, Buffer *b
|
|||||||
GotoCrumb PopCrumb(Array<GotoCrumb> *cr) {
|
GotoCrumb PopCrumb(Array<GotoCrumb> *cr) {
|
||||||
for (; cr->len;) {
|
for (; cr->len;) {
|
||||||
GotoCrumb c = Pop(cr);
|
GotoCrumb c = Pop(cr);
|
||||||
View *view = FindView(c.view_id, NULL);
|
View *view = GetView(c.view_id, NULL);
|
||||||
if (view) {
|
if (view) {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
@@ -247,12 +208,12 @@ GotoCrumb PopCrumb(Array<GotoCrumb> *cr) {
|
|||||||
View *GetLastValidView(Window *window) {
|
View *GetLastValidView(Window *window) {
|
||||||
For (IterateInReverse(&window->goto_redo)) {
|
For (IterateInReverse(&window->goto_redo)) {
|
||||||
if (it.view_id == window->active_view) continue;
|
if (it.view_id == window->active_view) continue;
|
||||||
View *view = FindView(it.view_id, NULL);
|
View *view = GetView(it.view_id, NULL);
|
||||||
if (view) return view;
|
if (view) return view;
|
||||||
}
|
}
|
||||||
For (IterateInReverse(&window->goto_history)) {
|
For (IterateInReverse(&window->goto_history)) {
|
||||||
if (it.view_id == window->active_view) continue;
|
if (it.view_id == window->active_view) continue;
|
||||||
View *view = FindView(it.view_id, NULL);
|
View *view = GetView(it.view_id, NULL);
|
||||||
if (view) return view;
|
if (view) return view;
|
||||||
}
|
}
|
||||||
return Views[0];
|
return Views[0];
|
||||||
@@ -303,3 +264,213 @@ void JumpForward(Window *window) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GotoNextInList(Window *window, Int line_offset = 1) {
|
||||||
|
Assert(line_offset == 1 || line_offset == -1);
|
||||||
|
View *active_view = GetView(window->active_view);
|
||||||
|
|
||||||
|
View *view_goto = GetView(window->active_goto_list);
|
||||||
|
window->active_view = view_goto->id;
|
||||||
|
|
||||||
|
Buffer *buffer_goto = GetBuffer(view_goto->active_buffer);
|
||||||
|
int64_t pos = window->goto_list_pos;
|
||||||
|
Int line = PosToLine(buffer_goto, pos);
|
||||||
|
|
||||||
|
bool opened = false;
|
||||||
|
for (Int i = line + line_offset; i >= 0 && i < buffer_goto->line_starts.len; i += line_offset) {
|
||||||
|
Range line_range = GetLineRangeWithoutNL(buffer_goto, i);
|
||||||
|
String16 string_line = GetString(buffer_goto, line_range);
|
||||||
|
|
||||||
|
{
|
||||||
|
Int idx = 0;
|
||||||
|
String16 delim = u"||>";
|
||||||
|
if (Seek(string_line, delim, &idx, SeekFlag_None)) {
|
||||||
|
string_line = Skip(string_line, idx + delim.len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view_goto->carets[0] = MakeCaret(line_range.min);
|
||||||
|
window->goto_list_pos = line_range.min;
|
||||||
|
string_line = Trim(string_line);
|
||||||
|
|
||||||
|
MergeCarets(buffer_goto, &view_goto->carets);
|
||||||
|
IF_DEBUG(AssertRanges(view_goto->carets));
|
||||||
|
if (string_line.len == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Range before_jump_range = active_view->carets[0].range;
|
||||||
|
|
||||||
|
BSet set = Open(string_line, ResolveOpenMeta_DontError | ResolveOpenMeta_DontExec);
|
||||||
|
if (set.window == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (set.view == active_view) {
|
||||||
|
if (AreOverlapping(set.view->carets[0].range, before_jump_range)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opened = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opened) window->active_view = active_view->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateScroll(Window *window, bool update_caret_scrolling) {
|
||||||
|
ProfileFunction();
|
||||||
|
BSet set = GetBSet(window);
|
||||||
|
|
||||||
|
// Scrolling with caret
|
||||||
|
if (update_caret_scrolling) {
|
||||||
|
Caret c = set.view->carets[0];
|
||||||
|
Int front = GetFront(c);
|
||||||
|
XY xy = PosToXY(set.buffer, front);
|
||||||
|
|
||||||
|
Rect2I visible = GetVisibleCells(window);
|
||||||
|
Vec2I visible_cells = GetSize(visible);
|
||||||
|
Vec2I visible_size = visible_cells * Vec2I{window->font->char_spacing, window->font->line_spacing};
|
||||||
|
Vec2I rect_size = GetSize(window->document_rect);
|
||||||
|
|
||||||
|
if (xy.line >= visible.max.y - 2) {
|
||||||
|
Int set_view_at_line = xy.line - (visible_cells.y - 1);
|
||||||
|
Int cut_off_y = Max((Int)0, visible_size.y - rect_size.y);
|
||||||
|
set.view->scroll.y = (set_view_at_line * window->font->line_spacing) + cut_off_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xy.line < visible.min.y + 1) {
|
||||||
|
set.view->scroll.y = xy.line * window->font->line_spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xy.col >= visible.max.x - 1) {
|
||||||
|
Int set_view_at_line = xy.col - (visible_cells.x - 1);
|
||||||
|
Int cut_off_x = Max((Int)0, visible_size.x - rect_size.x);
|
||||||
|
set.view->scroll.x = (set_view_at_line * window->font->char_spacing) + cut_off_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xy.col <= visible.min.x) {
|
||||||
|
set.view->scroll.x = xy.col * window->font->char_spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip scroll
|
||||||
|
{
|
||||||
|
Int last_line = LastLine(set.buffer);
|
||||||
|
set.view->scroll.y = Clamp(set.view->scroll.y, (Int)0, Max((Int)0, (last_line - 1) * window->font->line_spacing));
|
||||||
|
|
||||||
|
// @note:
|
||||||
|
// GetCharCountOfLongestLine is a bottleneck, there is probably an algorithm for
|
||||||
|
// calculating this value incrementally but do we even need X scrollbar or x clipping?
|
||||||
|
set.view->scroll.x = ClampBottom(set.view->scroll.x, (Int)0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CenterView(WindowID window) {
|
||||||
|
BSet set = GetBSet(window);
|
||||||
|
Caret c = set.view->carets[0];
|
||||||
|
Int front = GetFront(c);
|
||||||
|
XY xy = PosToXY(set.buffer, front);
|
||||||
|
Vec2I size = GetSize(set.window->document_rect);
|
||||||
|
Int y = size.y / 2 / set.window->font->line_spacing;
|
||||||
|
set.view->scroll.x = 0;
|
||||||
|
if (xy.line > y) {
|
||||||
|
set.view->scroll.y = (xy.line) * set.window->font->line_spacing - (size.y / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CenterPrimaryViews() {
|
||||||
|
For (Windows) {
|
||||||
|
if (it->primary) {
|
||||||
|
CenterView(it->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BSet GetBSet(Window *window) {
|
||||||
|
BSet set = {window};
|
||||||
|
set.view = GetView(set.window->active_view);
|
||||||
|
set.buffer = GetBuffer(set.view->active_buffer);
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
BSet GetBSet(WindowID window_id) {
|
||||||
|
Window *window = GetWindow(window_id);
|
||||||
|
BSet result = GetBSet(window);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String GetDirectory(Window *window) {
|
||||||
|
BSet set = GetBSet(window->id);
|
||||||
|
return GetDirectory(set.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
String GetPrimaryDirectory() {
|
||||||
|
BSet main = GetBSet(PrimaryWindowID);
|
||||||
|
return GetDirectory(main.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IsOnScreenResult {
|
||||||
|
bool caret_on_screen;
|
||||||
|
Int offset_from_top;
|
||||||
|
};
|
||||||
|
|
||||||
|
IsOnScreenResult IsMainCaretOnScreen(Window *window) {
|
||||||
|
BSet set = GetBSet(window);
|
||||||
|
Int front = GetFront(set.view->carets[0]);
|
||||||
|
XY front_xy = PosToXY(set.buffer, front);
|
||||||
|
Int caret_pixel_size = front_xy.y * set.window->font->line_spacing;
|
||||||
|
Vec2I doc_pixel_size = GetSize(set.window->document_rect);
|
||||||
|
|
||||||
|
Int ystart = set.view->scroll.y;
|
||||||
|
Int yend = ystart + doc_pixel_size.y;
|
||||||
|
bool caret_on_screen = caret_pixel_size >= ystart && caret_pixel_size < yend;
|
||||||
|
Int offset_from_top = caret_pixel_size - ystart;
|
||||||
|
return {caret_on_screen, offset_from_top};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetStoredOffsetFromTop(Window *window, IsOnScreenResult res) {
|
||||||
|
if (res.caret_on_screen) {
|
||||||
|
BSet set = GetBSet(window);
|
||||||
|
Int front = GetFront(set.view->carets[0]);
|
||||||
|
XY front_xy = PosToXY(set.buffer, front);
|
||||||
|
Int caret_pixel_size = front_xy.y * set.window->font->line_spacing;
|
||||||
|
set.view->scroll.y = caret_pixel_size - res.offset_from_top;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MoveCursorByPageSize(Window *window, int direction, bool shift = false) {
|
||||||
|
Assert(direction == DIR_UP || direction == DIR_DOWN);
|
||||||
|
BSet set = GetBSet(window);
|
||||||
|
IsOnScreenResult is_on_screen_res = IsMainCaretOnScreen(window);
|
||||||
|
Rect2I visible_cells_rect = GetVisibleCells(window);
|
||||||
|
Int y = GetSize(visible_cells_rect).y - 2;
|
||||||
|
if (direction == DIR_UP) {
|
||||||
|
y = -y;
|
||||||
|
}
|
||||||
|
|
||||||
|
For (set.view->carets) {
|
||||||
|
XY xy = PosToXY(set.buffer, GetFront(it));
|
||||||
|
xy.col = 0;
|
||||||
|
if (direction == DIR_DOWN && xy.line == set.buffer->line_starts.len - 1) {
|
||||||
|
Range line_range = GetLineRange(set.buffer, xy.line);
|
||||||
|
xy.col = line_range.max - line_range.min;
|
||||||
|
}
|
||||||
|
xy.line += y;
|
||||||
|
|
||||||
|
Int pos = XYToPosWithoutNL(set.buffer, xy);
|
||||||
|
if (shift) {
|
||||||
|
it = SetFront(it, pos);
|
||||||
|
} else {
|
||||||
|
it = MakeCaret(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IsOnScreenResult r = IsMainCaretOnScreen(window);
|
||||||
|
if (!r.caret_on_screen) {
|
||||||
|
SetStoredOffsetFromTop(window, is_on_screen_res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
struct Window;
|
|
||||||
struct WindowID { Int id; Window *o; };
|
|
||||||
|
|
||||||
struct Window {
|
|
||||||
WindowID id;
|
|
||||||
ViewID active_view;
|
|
||||||
|
|
||||||
Rect2I total_rect;
|
|
||||||
Rect2I document_rect;
|
|
||||||
Rect2I scrollbar_rect;
|
|
||||||
Rect2I line_numbers_rect;
|
|
||||||
Rect2I resizer_rect;
|
|
||||||
|
|
||||||
Font *font;
|
|
||||||
double mouse_scroller_offset;
|
|
||||||
int z;
|
|
||||||
double weight;
|
|
||||||
Caret search_bar_anchor;
|
|
||||||
|
|
||||||
GotoCrumb begin_frame_crumb;
|
|
||||||
Array<GotoCrumb> goto_history;
|
|
||||||
Array<GotoCrumb> goto_redo;
|
|
||||||
|
|
||||||
ViewID active_goto_list;
|
|
||||||
Int goto_list_pos;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
uint32_t draw_scrollbar : 1;
|
|
||||||
uint32_t draw_line_numbers : 1;
|
|
||||||
uint32_t secondary_window_style : 1;
|
|
||||||
uint32_t draw_line_highlight : 1;
|
|
||||||
uint32_t visible : 1;
|
|
||||||
uint32_t primary : 1;
|
|
||||||
uint32_t close : 1;
|
|
||||||
uint32_t sync_visibility_with_focus : 1;
|
|
||||||
uint32_t lose_focus_on_escape : 1;
|
|
||||||
uint32_t lose_visibility_on_escape : 1;
|
|
||||||
uint32_t jump_history : 1;
|
|
||||||
uint32_t skip_checkpoint : 1;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Scroller {
|
|
||||||
Rect2 rect;
|
|
||||||
double begin;
|
|
||||||
double end;
|
|
||||||
Int line_count;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline bool operator==(WindowID a, WindowID b) { return a.id == b.id; }
|
|
||||||
inline bool operator!=(WindowID a, WindowID b) { return a.id != b.id; }
|
|
||||||
Rect2I GetVisibleCells(Window *window);
|
|
||||||
Window *CreateWind();
|
|
||||||
|
|
||||||
void CommandWindowLayout(Rect2I *rect, Int wx, Int wy);
|
|
||||||
void CommandWindowInit();
|
|
||||||
|
|
||||||
void SearchWindowLayout(Rect2I *rect, Int wx, Int wy);
|
|
||||||
void SearchWindowInit();
|
|
||||||
|
|
||||||
void StatusWindowInit();
|
|
||||||
void StatusWindowLayout(Rect2I *rect, Int wx, Int wy);
|
|
||||||
|
|
||||||
void DebugWindowInit();
|
|
||||||
void DebugWindowLayout(Rect2I *rect, Int wx, Int wy);
|
|
||||||
|
|
||||||
void BuildWindowInit();
|
|
||||||
void BuildWindowLayout(Rect2I *rect, Int wx, Int wy);
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
void BuildWindowInit() {
|
|
||||||
Window *window = CreateWind();
|
|
||||||
BuildWindowID = window->id;
|
|
||||||
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "build"));
|
|
||||||
buffer->special = true;
|
|
||||||
buffer->no_history = true;
|
|
||||||
BuildBufferID = buffer->id;
|
|
||||||
View *view = CreateView(buffer->id);
|
|
||||||
view->special = true;
|
|
||||||
BuildViewID = view->id;
|
|
||||||
window->active_view = view->id;
|
|
||||||
window->secondary_window_style = true;
|
|
||||||
window->draw_line_highlight = true;
|
|
||||||
window->primary = false;
|
|
||||||
window->visible = false;
|
|
||||||
window->lose_visibility_on_escape = true;
|
|
||||||
window->jump_history = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BuildWindowLayout(Rect2I *rect, Int wx, Int wy) {
|
|
||||||
Window *n = GetWindow(BuildWindowID);
|
|
||||||
Rect2I copy_rect = *rect;
|
|
||||||
if (!n->visible) {
|
|
||||||
rect = ©_rect;
|
|
||||||
}
|
|
||||||
Int barsize = n->font->line_spacing * 10;
|
|
||||||
n->document_rect = n->total_rect = CutBottom(rect, barsize);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CMD_ShowBuildWindow() {
|
|
||||||
BSet main = GetBSet(BuildWindowID);
|
|
||||||
if (ActiveWindowID != BuildWindowID) {
|
|
||||||
main.window->visible = true;
|
|
||||||
NextActiveWindowID = BuildWindowID;
|
|
||||||
} else {
|
|
||||||
main.window->visible = false;
|
|
||||||
}
|
|
||||||
} RegisterCommand(CMD_ShowBuildWindow, "ctrl-grave");
|
|
||||||
@@ -1,398 +0,0 @@
|
|||||||
void CMD_ShowCommands() {
|
|
||||||
if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == CMD_ShowCommands) {
|
|
||||||
NextActiveWindowID = PrimaryWindowID;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ProfileFunction();
|
|
||||||
|
|
||||||
BSet command_bar = GetBSet(CommandWindowID);
|
|
||||||
command_bar.window->visible = true;
|
|
||||||
NextActiveWindowID = command_bar.window->id;
|
|
||||||
ResetBuffer(command_bar.buffer);
|
|
||||||
For (CommandFunctions) {
|
|
||||||
if (it.name == "OpenCommand") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// RawAppendf(command_bar.buffer, "\n:%-30S <|| :Set %-30S '%-30S'", it.name, it.name, it.binding);
|
|
||||||
RawAppendf(command_bar.buffer, "\n:%-30S <|| ", it.name);
|
|
||||||
if (it.doc.len) {
|
|
||||||
RawAppendf(command_bar.buffer, "%S", it.doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
command_bar.view->update_scroll = true;
|
|
||||||
SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer));
|
|
||||||
} RegisterCommand(CMD_ShowCommands, "ctrl-shift-p", "List available commands and their documentation inside the command window");
|
|
||||||
|
|
||||||
void CMD_ShowDebugBufferList() {
|
|
||||||
if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == CMD_ShowDebugBufferList) {
|
|
||||||
NextActiveWindowID = PrimaryWindowID;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ProfileFunction();
|
|
||||||
|
|
||||||
BSet command_bar = GetBSet(CommandWindowID);
|
|
||||||
command_bar.window->visible = true;
|
|
||||||
NextActiveWindowID = command_bar.window->id;
|
|
||||||
ResetBuffer(command_bar.buffer);
|
|
||||||
For (Buffers) {
|
|
||||||
bool is_special = it->special || it->temp || it->is_dir || it->dont_try_to_save_in_bulk_ops;
|
|
||||||
if (!is_special) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
RawAppendf(command_bar.buffer, "\n%S", it->name);
|
|
||||||
}
|
|
||||||
command_bar.view->update_scroll = true;
|
|
||||||
SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer));
|
|
||||||
} RegisterCommand(CMD_ShowDebugBufferList, "ctrl-shift-alt-p", "Show full list of buffers, including the special ones that normally just clutter list");
|
|
||||||
|
|
||||||
void CMD_ShowBufferList() {
|
|
||||||
if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == CMD_ShowBufferList) {
|
|
||||||
NextActiveWindowID = PrimaryWindowID;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ProfileFunction();
|
|
||||||
|
|
||||||
BSet command_bar = GetBSet(CommandWindowID);
|
|
||||||
command_bar.window->visible = true;
|
|
||||||
NextActiveWindowID = command_bar.window->id;
|
|
||||||
ResetBuffer(command_bar.buffer);
|
|
||||||
For (Buffers) {
|
|
||||||
bool is_special = it->special || it->temp || it->is_dir || it->dont_try_to_save_in_bulk_ops;
|
|
||||||
if (is_special) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
RawAppendf(command_bar.buffer, "\n%S", it->name);
|
|
||||||
}
|
|
||||||
command_bar.view->update_scroll = true;
|
|
||||||
SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer));
|
|
||||||
} RegisterCommand(CMD_ShowBufferList, "ctrl-p", "List open buffers inside the command window that you can fuzzy search over");
|
|
||||||
|
|
||||||
void OpenCommand(BSet active) {
|
|
||||||
ProfileFunction();
|
|
||||||
String16 string = FetchFuzzyViewLoadLine(active.view);
|
|
||||||
Open(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CMD_CommandWindowOpen() {
|
|
||||||
BSet active = GetBSet(ActiveWindowID);
|
|
||||||
BSet main = GetBSet(PrimaryWindowID);
|
|
||||||
NextActiveWindowID = main.window->id;
|
|
||||||
String16 string = FetchFuzzyViewLoadLine(active.view);
|
|
||||||
if (active.view->kind == ViewKind_ActiveSearch) {
|
|
||||||
main.window->active_goto_list = active.view->id;
|
|
||||||
main.window->goto_list_pos = active.view->carets[0].range.min;
|
|
||||||
}
|
|
||||||
Open(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommandWindowLayout(Rect2I *rect, Int wx, Int wy) {
|
|
||||||
Window *n = GetWindow(CommandWindowID);
|
|
||||||
Rect2I copy_rect = *rect;
|
|
||||||
if (!n->visible) {
|
|
||||||
rect = ©_rect;
|
|
||||||
}
|
|
||||||
Int barsize = Clamp((Int)n->font->line_spacing*10, (Int)0, (Int)wx - 100);
|
|
||||||
n->document_rect = n->total_rect = CutBottom(rect, barsize);
|
|
||||||
}
|
|
||||||
|
|
||||||
float NewFuzzyRate(String16 s, String16 p) {
|
|
||||||
float score = 0;
|
|
||||||
// try to do this: https://github.com/junegunn/fzf/blob/master/src/algo/algo.go
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
float FuzzyRate(String16 s, String16 p) {
|
|
||||||
float score = 0;
|
|
||||||
for (Int outer_pi = 0; outer_pi < p.len; outer_pi += 1) {
|
|
||||||
String16 pit = Skip(p, outer_pi);
|
|
||||||
if (IsWhitespace(At(pit, 0))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float matching = 0;
|
|
||||||
for (Int outer_si = 0; outer_si < s.len; outer_si += 1) {
|
|
||||||
String16 sit = Skip(s, outer_si);
|
|
||||||
if (IsWhitespace(At(sit, 0))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Int si = 0;
|
|
||||||
Int pi = 0;
|
|
||||||
for (;si < sit.len && pi < pit.len;) {
|
|
||||||
while (si < sit.len && IsWhitespace(sit[si])) si += 1;
|
|
||||||
while (pi < pit.len && IsWhitespace(pit[pi])) pi += 1;
|
|
||||||
if (pi >= pit.len) break;
|
|
||||||
if (si >= sit.len) break;
|
|
||||||
|
|
||||||
if (ToLowerCase(sit[si]) == ToLowerCase(pit[pi])) {
|
|
||||||
matching += 1.0f;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
si += 1;
|
|
||||||
pi += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
score += matching;
|
|
||||||
}
|
|
||||||
score = score / (float)s.len;
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool MergeSortCompare(FuzzyPair *a, FuzzyPair *b) {
|
|
||||||
bool result = a->rating > b->rating;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Array<FuzzyPair> FuzzySearchLines(Allocator allocator, Buffer *buffer, Int line_min, Int line_max, String16 needle) {
|
|
||||||
ProfileFunction();
|
|
||||||
if (line_min < 0 || line_min >= buffer->line_starts.len) return {};
|
|
||||||
if (line_max < 0 || line_min > buffer->line_starts.len) return {};
|
|
||||||
Array<FuzzyPair> ratings = {allocator};
|
|
||||||
Reserve(&ratings, line_max - line_min + 4);
|
|
||||||
for (Int i = line_min; i < line_max; i += 1) {
|
|
||||||
String16 s = GetLineStringWithoutNL(buffer, i);
|
|
||||||
|
|
||||||
Int idx = 0;
|
|
||||||
if (Seek(s, u"||>", &idx, SeekFlag_None)) {
|
|
||||||
s = GetPrefix(s, idx);
|
|
||||||
} else if (Seek(s, u"<||", &idx, SeekFlag_None)) {
|
|
||||||
s = GetPrefix(s, idx);
|
|
||||||
}
|
|
||||||
s = Trim(s);
|
|
||||||
|
|
||||||
float rating = FuzzyRate(s, needle);
|
|
||||||
Add(&ratings, {(int32_t)i, rating});
|
|
||||||
}
|
|
||||||
Array<FuzzyPair> temp = Copy(allocator, ratings);
|
|
||||||
MergeSort(ratings.len, ratings.data, temp.data);
|
|
||||||
return ratings;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SearchProjectParams {
|
|
||||||
String16 needle;
|
|
||||||
BufferID buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
void Coro_SearchProject(mco_coro *co) {
|
|
||||||
SearchProjectParams *param = (SearchProjectParams *)CoCurr->user_ctx;
|
|
||||||
|
|
||||||
Array<BufferID> buffers = {CoCurr->arena};
|
|
||||||
For (Buffers) {
|
|
||||||
Add(&buffers, it->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
ForItem (id, buffers) {
|
|
||||||
Buffer *it = GetBuffer(id, NULL);
|
|
||||||
if (it == NULL || it->special || it->is_dir || it->temp || it->dont_try_to_save_in_bulk_ops) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
Scratch scratch;
|
|
||||||
Array<Caret> occurences = FindAll(scratch, it, param->needle);
|
|
||||||
Buffer *out_buffer = GetBuffer(param->buffer);
|
|
||||||
ForItem (caret, occurences) {
|
|
||||||
Int pos = caret.range.min;
|
|
||||||
Int line = PosToLine(it, pos);
|
|
||||||
Range range = GetLineRangeWithoutNL(it, line);
|
|
||||||
Int column = pos - range.min;
|
|
||||||
String16 line_string = GetString(it, range);
|
|
||||||
String line_string8 = ToString(scratch, line_string);
|
|
||||||
RawAppendf(out_buffer, "%S ||> %S:%lld:%lld\n", line_string8, it->name, (long long)line + 1, (long long)column + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CoYield(co);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Coro_ReplaceAll(mco_coro *co) {
|
|
||||||
BSet main = GetBSet(PrimaryWindowID);
|
|
||||||
String16 string = FetchLoadWord(main.view);
|
|
||||||
String string8 = ToString(CoCurr->arena, string);
|
|
||||||
String16 needle = {};
|
|
||||||
String16 replace = {};
|
|
||||||
|
|
||||||
{
|
|
||||||
JumpTempBuffer(&main);
|
|
||||||
NextActiveWindowID = main.window->id;
|
|
||||||
RawAppendf(main.buffer, ":Submit (enter) :Cancel (escape)\nString to search for::%S", string8);
|
|
||||||
|
|
||||||
Caret field_seek = FindNext(main.buffer, u"for::", MakeCaret(0));
|
|
||||||
main.view->carets[0] = FindNext(main.buffer, string, field_seek);
|
|
||||||
AddHook(&main.view->hooks, "Submit", "enter", [](){BSet active = GetBSet(ActiveWindowID); active.view->hook_cmd = "Submit";});
|
|
||||||
AddHook(&main.view->hooks, "Cancel", "escape", [](){BSet active = GetBSet(ActiveWindowID); active.view->hook_cmd = "Cancel";});
|
|
||||||
|
|
||||||
String result = "Cancel";
|
|
||||||
for (;;) {
|
|
||||||
if (main.window->active_view != main.view->id || (main.window->id != PrimaryWindowID && main.window->id != ActiveWindowID) || main.window->close) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (main.view->hook_cmd != "") {
|
|
||||||
result = main.view->hook_cmd;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoYield(co);
|
|
||||||
}
|
|
||||||
Close(main.buffer->id);
|
|
||||||
if (result == "Cancel") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
field_seek = FindNext(main.buffer, u"for::", MakeCaret(0));
|
|
||||||
Range range = {field_seek.range.max, main.buffer->len};
|
|
||||||
needle = GetString(main.buffer, range);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
JumpTempBuffer(&main);
|
|
||||||
NextActiveWindowID = main.window->id;
|
|
||||||
RawAppendf(main.buffer, ":Submit (enter) :Cancel (escape)\nString to replace with::%S", string8);
|
|
||||||
|
|
||||||
Caret field_seek = FindNext(main.buffer, u"with::", MakeCaret(0));
|
|
||||||
main.view->carets[0] = FindNext(main.buffer, string, field_seek);
|
|
||||||
AddHook(&main.view->hooks, "Submit", "enter", [](){BSet active = GetBSet(ActiveWindowID); active.view->hook_cmd = "Submit";});
|
|
||||||
AddHook(&main.view->hooks, "Cancel", "escape", [](){BSet active = GetBSet(ActiveWindowID); active.view->hook_cmd = "Cancel";});
|
|
||||||
String result = "Cancel";
|
|
||||||
for (;;) {
|
|
||||||
if (main.window->active_view != main.view->id || (main.window->id != PrimaryWindowID && main.window->id != ActiveWindowID) || main.window->close) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (main.view->hook_cmd != "") {
|
|
||||||
result = main.view->hook_cmd;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoYield(co);
|
|
||||||
}
|
|
||||||
Close(main.buffer->id);
|
|
||||||
|
|
||||||
if (result == "Cancel") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
field_seek = FindNext(main.buffer, u"with::", MakeCaret(0));
|
|
||||||
Range range = {field_seek.range.max, main.buffer->len};
|
|
||||||
replace = GetString(main.buffer, range);
|
|
||||||
}
|
|
||||||
|
|
||||||
ForItem (buffer, Buffers) {
|
|
||||||
if (buffer->special || buffer->is_dir || buffer->temp || buffer->dont_try_to_save_in_bulk_ops) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
View *view = OpenBufferView(buffer->name);
|
|
||||||
if (SelectAllOccurences(view, needle)) {
|
|
||||||
Replace(view, replace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CMD_ReplaceAll() {
|
|
||||||
CoRemove("Coro_ReplaceAll");
|
|
||||||
CoData *data = CoAdd(Coro_ReplaceAll);
|
|
||||||
CoResume(data);
|
|
||||||
} RegisterCommand(CMD_ReplaceAll, "", "Search and replace over the entire project, you need to select a text with format like this 'FindAnd@>ReplaceWith' and executing the command will change all occurences of FindAnd to ReplaceWith");
|
|
||||||
|
|
||||||
void FuzzySearchViewUpdate() {
|
|
||||||
ProfileFunction();
|
|
||||||
BSet active = GetBSet(ActiveWindowID);
|
|
||||||
if (active.view->kind == ViewKind_ActiveSearch) {
|
|
||||||
Scratch scratch;
|
|
||||||
String16 line_string = GetLineStringWithoutNL(active.buffer, 0);
|
|
||||||
uint64_t hash = HashBytes(line_string.data, line_string.len * sizeof(char16_t));
|
|
||||||
if (active.view->prev_search_line_hash != hash) {
|
|
||||||
active.view->prev_search_line_hash = hash;
|
|
||||||
if (line_string.len > 0) {
|
|
||||||
// @todo: do we reintroduce history here?
|
|
||||||
Caret caret = active.view->carets[0];
|
|
||||||
SelectEntireBuffer(active.view);
|
|
||||||
Replace(active.view, line_string);
|
|
||||||
Append(active.view, "\n", false);
|
|
||||||
|
|
||||||
CoRemove("Coro_SearchProject");
|
|
||||||
CoData *dat = CoAdd(Coro_SearchProject);
|
|
||||||
SearchProjectParams *param = AllocType(dat->arena, SearchProjectParams);
|
|
||||||
param->needle = Copy16(dat->arena, line_string);
|
|
||||||
param->buffer = active.buffer->id;
|
|
||||||
dat->user_ctx = param;
|
|
||||||
dat->dont_wait_until_resolved = true;
|
|
||||||
CoResume(dat);
|
|
||||||
|
|
||||||
active.view->carets[0] = caret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (active.view->kind == ViewKind_FuzzySearch) {
|
|
||||||
Scratch scratch;
|
|
||||||
String16 line_string = GetLineStringWithoutNL(active.buffer, 0);
|
|
||||||
uint64_t hash = HashBytes(line_string.data, line_string.len * sizeof(char16_t));
|
|
||||||
if (active.view->prev_search_line_hash != hash) {
|
|
||||||
active.view->prev_search_line_hash = hash;
|
|
||||||
Array<FuzzyPair> ratings = FuzzySearchLines(scratch, active.buffer, 1, active.buffer->line_starts.len, line_string);
|
|
||||||
|
|
||||||
Buffer *scratch_buff = CreateScratchBuffer(scratch, active.buffer->cap);
|
|
||||||
RawAppend(scratch_buff, line_string);
|
|
||||||
For(IterateInReverse(&ratings)) {
|
|
||||||
String16 s = GetLineStringWithoutNL(active.buffer, it.index);
|
|
||||||
if (s.len == 0) continue;
|
|
||||||
RawAppend(scratch_buff, u"\n");
|
|
||||||
RawAppend(scratch_buff, s);
|
|
||||||
}
|
|
||||||
|
|
||||||
Caret caret = active.view->carets[0];
|
|
||||||
SaveCaretHistoryBeforeBeginEdit(active.buffer, active.view->carets);
|
|
||||||
SelectEntireBuffer(active.view);
|
|
||||||
Replace(active.view, GetString(scratch_buff));
|
|
||||||
active.view->carets[0] = caret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CMD_SearchProject() {
|
|
||||||
BSet main = GetBSet(PrimaryWindowID);
|
|
||||||
String16 string = {};
|
|
||||||
if (main.view->carets.len == 1 && GetSize(main.view->carets[0]) > 0) {
|
|
||||||
string = GetString(main.buffer, main.view->carets[0].range);
|
|
||||||
}
|
|
||||||
|
|
||||||
NextActiveWindowID = main.window->id;
|
|
||||||
Buffer *search_project_buffer = GetBuffer(SearchProjectBufferID);
|
|
||||||
View *view = WindowOpenBufferView(main.window, search_project_buffer->name);
|
|
||||||
view->special = true;
|
|
||||||
view->kind = ViewKind_ActiveSearch;
|
|
||||||
AddHook(&view->hooks, "Open", "ctrl-q | enter | f12", CMD_CommandWindowOpen);
|
|
||||||
SelectRange(view, GetLineRangeWithoutNL(search_project_buffer, 0));
|
|
||||||
if (string.len) {
|
|
||||||
Replace(view, string);
|
|
||||||
SelectEntireBuffer(view);
|
|
||||||
}
|
|
||||||
} RegisterCommand(CMD_SearchProject, "ctrl-shift-f", "Interactive search over the entire project in a new buffer view");
|
|
||||||
|
|
||||||
void SetFuzzy(View *view) {
|
|
||||||
view->kind = ViewKind_FuzzySearch;
|
|
||||||
AddHook(&view->hooks, "Open", "ctrl-q | enter | f12", CMD_CommandWindowOpen);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommandWindowInit() {
|
|
||||||
Window *window = CreateWind();
|
|
||||||
CommandWindowID = window->id;
|
|
||||||
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "command_bar"));
|
|
||||||
buffer->special = true;
|
|
||||||
buffer->no_history = true;
|
|
||||||
View *view = CreateView(buffer->id);
|
|
||||||
view->special = true;
|
|
||||||
SetFuzzy(view);
|
|
||||||
window->active_view = view->id;
|
|
||||||
window->draw_line_numbers = false;
|
|
||||||
window->draw_scrollbar = false;
|
|
||||||
window->secondary_window_style = true;
|
|
||||||
window->draw_line_highlight = true;
|
|
||||||
window->primary = false;
|
|
||||||
window->visible = false;
|
|
||||||
window->sync_visibility_with_focus = true;
|
|
||||||
window->lose_focus_on_escape = true;
|
|
||||||
window->jump_history = false;
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
void DebugWindowInit() {
|
|
||||||
Window *window = CreateWind();
|
|
||||||
DebugWindowID = window->id;
|
|
||||||
window->draw_line_numbers = false;
|
|
||||||
window->draw_scrollbar = false;
|
|
||||||
window->visible = false;
|
|
||||||
window->z = 2;
|
|
||||||
window->primary = false;
|
|
||||||
window->jump_history = false;
|
|
||||||
|
|
||||||
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "debug"));
|
|
||||||
DebugBufferID = buffer->id;
|
|
||||||
buffer->no_history = true;
|
|
||||||
buffer->special = true;
|
|
||||||
|
|
||||||
View *view = CreateView(buffer->id);
|
|
||||||
view->special = true;
|
|
||||||
DebugViewID = view->id;
|
|
||||||
window->active_view = view->id;
|
|
||||||
window->visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DebugWindowLayout(Rect2I *rect, Int wx, Int wy) {
|
|
||||||
Window *n = GetWindow(DebugWindowID);
|
|
||||||
Rect2 screen_rect = Rect0Size((float)wx, (float)wy);
|
|
||||||
Vec2 size = GetSize(screen_rect);
|
|
||||||
|
|
||||||
Rect2 a = CutRight(&screen_rect, 0.3f * size.x);
|
|
||||||
Rect2 b = CutTop(&a, 0.4f * size.y);
|
|
||||||
Rect2 c = Shrink(b, 20);
|
|
||||||
n->document_rect = n->total_rect = ToRect2I(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DebugWindowUpdate() {
|
|
||||||
ProfileFunction();
|
|
||||||
BSet set = GetBSet(DebugWindowID);
|
|
||||||
if (!set.window->visible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BSet active = GetBSet(ActiveWindowID);
|
|
||||||
if (active.buffer->id.id == set.buffer->id.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BSet main = GetBSet(PrimaryWindowID);
|
|
||||||
|
|
||||||
Scratch scratch;
|
|
||||||
String s = Format(scratch, "wid: %d\nvid: %d\nbid: %d\nframe: %lld\n", (int)main.window->id.id, (int)main.view->id.id, (int)main.buffer->id.id, (long long)FrameID);
|
|
||||||
String16 string = ToString16(scratch, s);
|
|
||||||
RawReplaceText(set.buffer, GetRange(set.buffer), string);
|
|
||||||
|
|
||||||
float xmouse, ymouse;
|
|
||||||
SDL_GetMouseState(&xmouse, &ymouse);
|
|
||||||
RawAppendf(set.buffer, "mouse: [%f, %f]\n", roundf(DPIScale * xmouse), roundf(DPIScale * ymouse));
|
|
||||||
|
|
||||||
RawAppendf(set.buffer, "BufferID id = %d\n", main.buffer->id.id);
|
|
||||||
RawAppendf(set.buffer, "String name = %S\n", main.buffer->name);
|
|
||||||
RawAppendf(set.buffer, "Int change_id = %lld\n", (long long)main.buffer->change_id);
|
|
||||||
RawAppendf(set.buffer, "Int user_change_id = %lld\n", (long long)main.buffer->user_change_id);
|
|
||||||
RawAppendf(set.buffer, "Int file_mod_time = %lld\n", (long long)main.buffer->file_mod_time);
|
|
||||||
RawAppendf(set.buffer, "\n");
|
|
||||||
|
|
||||||
RawAppendf(set.buffer, "U16 *data = %zu\n", main.buffer->data);
|
|
||||||
RawAppendf(set.buffer, "Int len = %lld\n", (long long)main.buffer->len);
|
|
||||||
RawAppendf(set.buffer, "Int cap = %lld\n", (long long)main.buffer->cap);
|
|
||||||
RawAppendf(set.buffer, "Array<Int> line_starts = {len = %lld, cap = %lld, data = %zu}\n", (long long)main.buffer->line_starts.len, (long long)main.buffer->line_starts.cap, main.buffer->line_starts.data);
|
|
||||||
RawAppendf(set.buffer, "\n");
|
|
||||||
|
|
||||||
RawAppendf(set.buffer, "Array<HistoryEntry> undo_stack = {len = %lld, cap = %lld, data = %zu}\n", (long long)main.buffer->undo_stack.len, (long long)main.buffer->undo_stack.cap, main.buffer->undo_stack.data);
|
|
||||||
RawAppendf(set.buffer, "Array<HistoryEntry> redo_stack = {len = %lld, cap = %lld, data = %zu}\n", (long long)main.buffer->redo_stack.len, (long long)main.buffer->redo_stack.cap, main.buffer->redo_stack.data);
|
|
||||||
RawAppendf(set.buffer, "int edit_phase = %d", main.buffer->edit_phase);
|
|
||||||
RawAppendf(set.buffer, "\n");
|
|
||||||
|
|
||||||
RawAppendf(set.buffer, "int no_history = %d\n", main.buffer->no_history);
|
|
||||||
RawAppendf(set.buffer, "int no_line_starts = %d\n", main.buffer->no_line_starts);
|
|
||||||
RawAppendf(set.buffer, "int dirty = %d\n", main.buffer->dirty);
|
|
||||||
RawAppendf(set.buffer, "int changed_on_disk = %d\n", main.buffer->changed_on_disk);
|
|
||||||
RawAppendf(set.buffer, "int temp = %d\n", main.buffer->temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CMD_ToggleDebug() {
|
|
||||||
Window *window = GetWindow(DebugWindowID);
|
|
||||||
window->visible = !window->visible;
|
|
||||||
} RegisterCommand(CMD_ToggleDebug, "ctrl-0", "Open a floating window that might become useful for debugging");
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
void StatusWindowInit() {
|
|
||||||
Window *window = CreateWind();
|
|
||||||
StatusBarWindowID = window->id;
|
|
||||||
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(WorkDir, "status_bar"));
|
|
||||||
buffer->special = true;
|
|
||||||
View *view = CreateView(buffer->id);
|
|
||||||
view->special = true;
|
|
||||||
window->active_view = view->id;
|
|
||||||
// window->font = &SecondaryFont;
|
|
||||||
window->draw_line_numbers = false;
|
|
||||||
window->draw_scrollbar = false;
|
|
||||||
window->draw_line_highlight = true;
|
|
||||||
window->secondary_window_style = true;
|
|
||||||
window->primary = false;
|
|
||||||
window->jump_history = false;
|
|
||||||
window->lose_focus_on_escape = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusWindowLayout(Rect2I *rect, Int wx, Int wy) {
|
|
||||||
Window *n = GetWindow(StatusBarWindowID);
|
|
||||||
Rect2I copy_rect = *rect;
|
|
||||||
if (!n->visible) {
|
|
||||||
rect = ©_rect;
|
|
||||||
}
|
|
||||||
Int barsize = GetExpandingBarSize(n);
|
|
||||||
n->document_rect = n->total_rect = CutBottom(rect, barsize);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatusWindowUpdate() {
|
|
||||||
ProfileFunction();
|
|
||||||
Window *status_bar_window = GetWindow(StatusBarWindowID, NULL);
|
|
||||||
Scratch scratch;
|
|
||||||
BSet main = GetBSet(PrimaryWindowID);
|
|
||||||
BSet title = GetBSet(status_bar_window);
|
|
||||||
title.view->scroll.y = 0;
|
|
||||||
|
|
||||||
String16 buffer_string = GetString(title.buffer);
|
|
||||||
Range replace_range = {0, title.buffer->len};
|
|
||||||
bool found_separator = Seek(buffer_string, u" |", &replace_range.max);
|
|
||||||
|
|
||||||
// Parse the title and line
|
|
||||||
if (title.window->id == ActiveWindowID) {
|
|
||||||
if (title.buffer->change_id == title.buffer->begin_frame_change_id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String16 buffer_name = GetString(title.buffer, replace_range);
|
|
||||||
buffer_name = Trim(buffer_name);
|
|
||||||
|
|
||||||
Int column = ChopNumber(&buffer_name);
|
|
||||||
if (column == -1) return;
|
|
||||||
|
|
||||||
Chop(&buffer_name, u":");
|
|
||||||
Int line = ChopNumber(&buffer_name);
|
|
||||||
if (line == -1) {
|
|
||||||
line = column;
|
|
||||||
column = 0;
|
|
||||||
}
|
|
||||||
Chop(&buffer_name, u":");
|
|
||||||
|
|
||||||
Int buffer_pos = XYToPos(main.buffer, {column, line});
|
|
||||||
Caret &caret = main.view->carets[0];
|
|
||||||
if (GetFront(caret) != buffer_pos) {
|
|
||||||
caret = MakeCaret(buffer_pos);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Caret caret = main.view->carets[0];
|
|
||||||
XY xy = PosToXY(main.buffer, GetFront(caret));
|
|
||||||
|
|
||||||
// add separator at the end of buffer
|
|
||||||
if (!found_separator) {
|
|
||||||
SelectRange(title.view, GetBufferEndAsRange(title.buffer));
|
|
||||||
ReplaceEx(scratch, title.view, u" | :Prev :Next :Close");
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace data up to separator with filename and stuff
|
|
||||||
const char *reopen = main.buffer->changed_on_disk ? " :Reopen" : "";
|
|
||||||
const char *case_sens = SearchCaseSensitive ? " C" : "";
|
|
||||||
const char *word_bound = SearchWordBoundary ? " W" : "";
|
|
||||||
const char *dirty = main.buffer->dirty ? " !" : "";
|
|
||||||
String s = Format(scratch, "%S:%lld:%lld%s%s%s", main.buffer->name, (long long)xy.line + 1ll, (long long)xy.col + 1ll, dirty, case_sens, word_bound, reopen);
|
|
||||||
For (ActiveProcesses) {
|
|
||||||
if (it.view_id == main.view->id.id) {
|
|
||||||
s = Format(scratch, "%S %lld :KillProcess", s, (long long)it.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String16 string = ToString16(scratch, s);
|
|
||||||
String16 string_to_replace = GetString(title.buffer, replace_range);
|
|
||||||
if (string_to_replace != string) {
|
|
||||||
SelectRange(title.view, replace_range);
|
|
||||||
ReplaceEx(scratch, title.view, string);
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectRange(title.view, MakeRange(0));
|
|
||||||
ResetHistory(title.buffer);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user