Compare commits

...

61 Commits

Author SHA1 Message Date
Krzosa Karol
3ab6639afb Fix underline drawing over line numbers 2026-02-04 08:26:32 +01:00
Krzosa Karol
7a73a982d2 Pause on lexing stuff 2026-02-03 23:23:49 +01:00
Krzosa Karol
51f1fd1b4f ProjectDirectory to ProjectFolder 2026-02-03 21:11:02 +01:00
Krzosa Karol
930620a49e Init variables, ReportErrorf now doesn't pop a message cause it doesn't do the queuing 2026-02-03 21:10:33 +01:00
Krzosa Karol
8cb1b49cd8 Fix ctrl-4 2026-02-03 20:06:56 +01:00
Krzosa Karol
830be12b24 Major redesign of the Exec API, in order to make linux more uniform with windows and introduce python shell 2026-02-02 22:21:19 +01:00
Krzosa Karol
ed3be39037 Begin data_desc 2026-02-01 17:45:15 +01:00
Krzosa Karol
33d623268a Fix outside of buffer access and add docs 2026-02-01 12:28:34 +01:00
Krzosa Karol
5198c11fc6 CMD_OpenScratch 2026-02-01 11:21:21 +01:00
Krzosa Karol
bc7c52e1ec Fix formatting crash 2026-02-01 11:16:14 +01:00
Krzosa Karol
bbf97eba2f Fixing issues, enabled warnings and fixing on linux 2026-01-31 22:53:18 +01:00
Krzosa Karol
52390a7aa8 Update build for linux 2026-01-31 22:47:03 +01:00
Krzosa Karol
d5d99cddf7 RunFile python, open scratch buffer what ever is already there 2026-01-31 10:45:56 +01:00
Krzosa Karol
217659256b Begin design of comment evaluation and {{variables}} 2026-01-31 09:49:44 +01:00
Krzosa Karol
4d9cfcd302 MergeCarets in SelectCOmment 2026-01-31 07:30:40 +01:00
Krzosa Karol
d5099cee38 PageDown PageUp don't change if caret still on screen 2026-01-31 07:30:27 +01:00
Krzosa Karol
017b70f3e6 In debug build set position 2026-01-31 07:30:06 +01:00
Krzosa Karol
903159d2bd SelectComment 2026-01-30 22:16:22 +01:00
Krzosa Karol
a87d2491bb Add warnings in remedy and remove QueryUserFile (not very good currently) 2026-01-30 22:16:16 +01:00
Krzosa Karol
70b752eccb Fix line number clipping, rendering issue 2026-01-30 21:44:46 +01:00
Krzosa Karol
805f5de852 Coroutine API rename 2026-01-30 21:44:28 +01:00
Krzosa Karol
c9acc31cfc PageDown, PageUp is setting cursor position to beginning of line now 2026-01-30 21:43:01 +01:00
Krzosa Karol
2d1edd800a UpdateCoroutines 2026-01-30 20:03:22 +01:00
Krzosa Karol
22a82db946 When jumping to command, first line is for issuing next command easily 2026-01-30 19:48:50 +01:00
Krzosa Karol
4e8987101d Addressing the C:/Program Files (x86)/ situation 2026-01-30 19:34:05 +01:00
Krzosa Karol
034ac5d452 Fix ctrl-x on last line 2026-01-30 08:31:59 +01:00
Krzosa Karol
9c34a4dd52 Change bindings for build 2026-01-30 08:31:47 +01:00
Krzosa Karol
0c488bc313 Fix indenting, dedenting 2026-01-25 17:53:29 +01:00
Krzosa Karol
1c25b39df0 Scrolling and word complete improvements 2026-01-25 12:02:06 +01:00
Krzosa Karol
2454370736 New behavior only underline for caret on ctrl 2026-01-25 09:22:17 +01:00
Krzosa Karol
eec1e137b7 ctrl-space for word-complete? 2026-01-25 09:14:26 +01:00
Krzosa Karol
a9730924d7 CheckKeybindingColission 2026-01-25 09:11:45 +01:00
Krzosa Karol
88dbfb1a6c Fix ctrl-f immediate jump when selecting a word 2026-01-25 09:05:07 +01:00
Krzosa Karol
f3e709f07c Keybinding overlapp fix 2026-01-25 09:04:49 +01:00
Krzosa Karol
7ae4e74f50 SLOW_BUILD, 2 additional build targets, improve word complete 2026-01-25 08:54:00 +01:00
Krzosa Karol
863f140edc WordComplete working 2026-01-25 00:16:23 +01:00
Krzosa Karol
3618d906e6 Begin WordComplete 2026-01-24 18:14:38 +01:00
Krzosa Karol
26691ab6d3 Try to fix the indenting again 2026-01-24 17:16:49 +01:00
Krzosa Karol
923131a5b6 Fix remedy crash on f9 and scratch content reset to normal 2026-01-24 14:22:05 +01:00
Krzosa Karol
7b47f2f6f5 Indent lines make selection nicer 2026-01-24 14:12:06 +01:00
Krzosa Karol
f5825f050c Fix EndEdit when edits->len == 0 2026-01-24 14:11:40 +01:00
Krzosa Karol
05b6a7bb3c Remove IndentString 2026-01-24 13:54:39 +01:00
Krzosa Karol
cc69d807e3 Fix modifiers getting stuck after attaching debugger, at least for windows.. 2026-01-24 13:48:32 +01:00
Krzosa Karol
dc839cb3e0 Fix the line moving, really hard code ... 2026-01-24 13:48:08 +01:00
Krzosa Karol
ba6de21396 Query file add UI indicator (but need something better ...), fix remedy 2026-01-23 22:36:11 +01:00
Krzosa Karol
b75e0a8a55 Add '..' to file listing 2026-01-23 22:35:12 +01:00
Krzosa Karol
9a64fd8e01 BIG OPTIMIZATIONS: WTF IT WORKS SO FAST NOW, 9000 files open easy WHAAAT 2026-01-22 22:59:17 +01:00
Krzosa Karol
b0a0fca8ee SelfAttachDebugger command 2026-01-22 10:43:22 +01:00
Krzosa Karol
710269a876 plugin_file_commands 2026-01-22 10:25:34 +01:00
Krzosa Karol
4a410c3c1a Profiler as plugin 2026-01-22 08:57:56 +01:00
Krzosa Karol
4fa1a49765 :BeginProfile :EndProfile 2026-01-21 23:00:59 +01:00
Krzosa Karol
9614f01e20 Build1 and Build2 2026-01-21 22:29:38 +01:00
Krzosa Karol
36839df841 Make GotoNextInList only skip things that are in range of first caret 2026-01-21 09:05:30 +01:00
Krzosa Karol
2b1eab54b3 Since we are doing column searches, don't skip entries in GoToNext on same line 2026-01-21 09:00:07 +01:00
Krzosa Karol
8128c2932e Fix search anchor with ctrl-f 2026-01-21 08:59:32 +01:00
Krzosa Karol
851eac0ec2 Try open with selection always opening first line 2026-01-21 08:59:21 +01:00
Krzosa Karol
df963b0893 LogBuffer and NullBuffer separation, More indicators in status as new line as sep, eval multiple carets with Open, :Errors 2026-01-21 08:28:12 +01:00
Krzosa Karol
84c992c574 CreateProject OpenProject 2026-01-19 23:16:32 +01:00
Krzosa Karol
1ce128a5d5 CoroutineLeakCheck 2026-01-19 09:08:26 +01:00
Krzosa Karol
372287f557 CMD_SetProjectHere, look for project files 2026-01-19 08:44:05 +01:00
Krzosa Karol
8ecfc82d8f RemedyBGPlugin: Look for .rdbg file in project dir if BinaryUnderDebug not set 2026-01-19 08:43:52 +01:00
51 changed files with 2765 additions and 1598 deletions

2
.gitignore vendored
View File

@@ -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

View File

@@ -6,6 +6,7 @@ for %%a in (%*) do set "%%~a=1"
if not "%release%"=="1" set debug=1 if not "%release%"=="1" set debug=1
if "%debug%"=="1" set release=0 && echo [debug mode] if "%debug%"=="1" set release=0 && echo [debug mode]
if "%release%"=="1" set debug=0 && echo [release mode] if "%release%"=="1" set debug=0 && echo [release mode]
if "%slow%"=="1" echo [slow mode]
if "%package%"=="1" echo [package zip] if "%package%"=="1" echo [package zip]
if not exist "src\external\SDL" ( if not exist "src\external\SDL" (
@@ -28,6 +29,7 @@ pushd build
set flags=/EHsc- /MT /Zi /FC /nologo /WX /W3 /wd4200 /wd4334 /diagnostics:column set flags=/EHsc- /MT /Zi /FC /nologo /WX /W3 /wd4200 /wd4334 /diagnostics:column
if "%release%"=="1" set flags=%flags% -O2 -DDEBUG_BUILD=0 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
rem rc ..\data\icon.rc rem rc ..\data\icon.rc

View File

@@ -1,10 +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"
I="-I../src/external/SDL/include -I../src/external/lua/src -I../src/external/glad" flags="-Wall -Wextra -Werror -Wformat=2 -Wundef -Wshadow -Wno-missing-field-initializers -Wno-missing-braces -Wno-writable-strings \
clang -o te $FLAGS ../src/text_editor/text_editor.cpp $I -lSDL3 -lm -lbacktrace -g -fdiagnostics-absolute-paths \
-nostdlib++ -fno-exceptions"
# -v -Wl,--verbose if [ -v debug ]; then flags="$flags -fsanitize=address,undefined -fno-omit-frame-pointer -DDEBUG_BUILD=1"; fi
if [ -v release ]; then flags="$flags -DDEBUG_BUILD=0 -O2"; fi
if [ -v slow ]; then flags="$flags -DSLOW_BUILD=1"; fi
time clang -o te $flags ../src/text_editor/text_editor.cpp $I -lSDL3 -lm -lbacktrace

View File

@@ -1,32 +1,17 @@
! What precise workflow do I need for me to be viable to use this? ! What precise workflow do I need for me to be viable to use this?
! 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???)
- We need regex for: [FormatCode matching, IsCode matching, - We need regex for: [FormatCode matching, IsCode matching,
- Macros - Macros
- 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()
- 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>> <<ProjectDirectory>> 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 SearchOpenBuffers 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:
@@ -48,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!
@@ -72,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

View File

@@ -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);

View File

@@ -28,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
/////////////////// ///////////////////
@@ -47,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}; }
}; };
@@ -66,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);
@@ -112,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; }

View File

@@ -211,9 +211,8 @@ 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;
} }
@@ -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};
} }

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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) {}

View File

@@ -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;
} }

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;
} }

View File

@@ -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);
} }

View File

@@ -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;
@@ -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;
@@ -306,13 +304,8 @@ API Int GetWordEnd(Buffer *buffer, Int pos) {
return pos; return pos;
} }
bool IsOpenBoundary(char c) {
bool result = c == 0 || IsParen(c) || IsBrace(c) || c == ':' || c == '\t' || c == '\n' || c == '"' || c == '\'';
return result;
}
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));
} }
@@ -422,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;
@@ -469,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;
@@ -671,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);
@@ -745,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.
@@ -798,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
if (buffer->no_line_starts == false) {
Assert(buffer->line_starts.len); 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);
@@ -823,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
@@ -899,7 +893,7 @@ API void RedoEdit(Buffer *buffer, Array<Caret> *carets) {
ProfileFunction(); ProfileFunction();
if (buffer->no_history) return; if (buffer->no_history) return;
for (int i = 0; buffer->redo_stack.len > 0; i += 1) { for (;buffer->redo_stack.len > 0;) {
HistoryEntry entry = Pop(&buffer->redo_stack); HistoryEntry entry = Pop(&buffer->redo_stack);
HistoryEntry *e = SaveHistoryBeforeMergeCursor(buffer, &buffer->undo_stack, *carets); HistoryEntry *e = SaveHistoryBeforeMergeCursor(buffer, &buffer->undo_stack, *carets);
e->time = entry.time; e->time = entry.time;
@@ -1039,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();
{ {
@@ -1058,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);
@@ -1069,6 +1069,7 @@ API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool
} }
#endif #endif
if (skip_fixing_carets_user_will_do_that_himself == false) {
// Adjust carets // Adjust carets
// this one also moves the carets forward if they are aligned with edit // this one also moves the carets forward if they are aligned with edit
// //
@@ -1106,6 +1107,7 @@ API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool
} }
} }
} }
}
// Merge carets that overlap, this needs to be handled before any edits to // Merge carets that overlap, this needs to be handled before any edits to
// make sure overlapping edits won't happen. // make sure overlapping edits won't happen.
@@ -1239,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');
@@ -1254,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);
@@ -1323,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);
@@ -1347,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);
@@ -1368,10 +1370,24 @@ 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]) {
@@ -1432,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;
} }
@@ -1506,18 +1522,6 @@ 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;
@@ -1553,7 +1557,16 @@ void SaveBuffer(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 SaveAll() {
For(Buffers) {
// 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);
}
} }
} }
@@ -1566,3 +1579,15 @@ String GetDirectory(Buffer *buffer) {
String result = ChopLastSlash(buffer->name); String result = ChopLastSlash(buffer->name);
return result; 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);
}
}
}
}

View File

@@ -1,34 +1,5 @@
View *GetViewForFixingWhenBufferCommand(Buffer *buffer, bool *is_active = NULL) {
View *view = NULL;
if (is_active) {
*is_active = false;
}
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;
}
void ReplaceWithoutMovingCarets(Buffer *buffer, Range range, String16 string) { void ReplaceWithoutMovingCarets(Buffer *buffer, Range range, String16 string) {
View *view = GetViewForFixingWhenBufferCommand(buffer); View *view = GetViewForBuffer(buffer);
Array<Caret> carets = Copy(GetSystemAllocator(), view->carets); Array<Caret> carets = Copy(GetSystemAllocator(), view->carets);
Scratch scratch; Scratch scratch;
@@ -96,43 +67,26 @@ void Appendf(View *view, const char *fmt, ...) {
Append(view, string, true); Append(view, string, true);
} }
void ReportErrorf(const char *fmt, ...) {
Scratch scratch;
STRING_FORMAT(scratch, fmt, string);
if (BreakOnError) {
BREAK();
}
View *view = GetView(NullViewID);
if (view) {
Appendf(view, "%S\n", string);
}
ShowUIMessagef("%S", string);
}
void ReportConsolef(const char *fmt, ...) { void ReportConsolef(const char *fmt, ...) {
Scratch scratch; Scratch scratch;
STRING_FORMAT(scratch, fmt, string); STRING_FORMAT(scratch, fmt, string);
View *view = GetView(NullViewID); Appendf(LogView, "%S\n", string);
Appendf(view, "%S\n", string);
} }
void ReportWarningf(const char *fmt, ...) { void ReportErrorf(const char *fmt, ...) {
ErrorCount += 1;
Scratch scratch; Scratch scratch;
STRING_FORMAT(scratch, fmt, string); STRING_FORMAT(scratch, fmt, string);
if (BreakOnError) { if (BreakOnError) {
BREAK(); BREAK();
} }
View *null_view = GetView(NullViewID);
Appendf(null_view, "%S\n", string);
}
void CMD_OpenLoadWord() { if (LogView) {
Scratch scratch; Appendf(LogView, "%S\n", string);
BSet active = GetBSet(ActiveWindowID); } else {
String16 load_word = FetchLoadWord(active.view); printf("%.*s\n", (int)string.len, string.data);
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_CenterView() { void CMD_CenterView() {
CenterView(PrimaryWindowID); CenterView(PrimaryWindowID);
@@ -142,7 +96,7 @@ void TrimWhitespace(Buffer *buffer, bool trim_lines_with_caret) {
Scratch scratch; Scratch scratch;
bool is_active_view = false; bool is_active_view = false;
View *view = GetViewForFixingWhenBufferCommand(buffer, &is_active_view); View *view = GetViewForBuffer(buffer, &is_active_view);
if (!is_active_view && !trim_lines_with_caret) { if (!is_active_view && !trim_lines_with_caret) {
trim_lines_with_caret = true; trim_lines_with_caret = true;
} }
@@ -152,7 +106,7 @@ void TrimWhitespace(Buffer *buffer, bool trim_lines_with_caret) {
Array<Range> lines_to_skip_triming = {}; Array<Range> lines_to_skip_triming = {};
if (!trim_lines_with_caret) { if (!trim_lines_with_caret) {
lines_to_skip_triming = GetSelectedLinesSorted(scratch, view); lines_to_skip_triming = GetSelectedLinesSortedExclusive(scratch, view);
} }
for (Int i = 0; i < buffer->line_starts.len; i += 1) { for (Int i = 0; i < buffer->line_starts.len; i += 1) {
@@ -171,7 +125,7 @@ void TrimWhitespace(Buffer *buffer, bool trim_lines_with_caret) {
AddEdit(&edits, whitespace_range, u""); AddEdit(&edits, whitespace_range, u"");
} }
EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION); EndEdit(buffer, &edits, &view->carets, !EndEdit_KillSelection);
view->update_scroll = false; view->update_scroll = false;
} }
@@ -183,11 +137,11 @@ void ApplyFormattingTool(Buffer *buffer, String tool) {
if (exec_result.exit_code == 0) { if (exec_result.exit_code == 0) {
ReplaceWithoutMovingCarets(buffer, GetRange(buffer), string16); ReplaceWithoutMovingCarets(buffer, GetRange(buffer), string16);
} else { } else {
View *view = GetView(NullViewID); Append(LogView, string16, true);
Append(view, string16, true);
} }
} }
// @todo: plugin_languages ? and then branch out language_cpp ...
void CMD_FormatSelection() { void CMD_FormatSelection() {
Scratch scratch; Scratch scratch;
BSet primary = GetBSet(PrimaryWindowID); BSet primary = GetBSet(PrimaryWindowID);
@@ -209,239 +163,25 @@ void CMD_FormatSelection() {
String16 string16 = {exec_result.buffer->str, exec_result.buffer->len}; String16 string16 = {exec_result.buffer->str, exec_result.buffer->len};
AddEdit(&edits, it.range, string16); AddEdit(&edits, it.range, string16);
} }
EndEdit(primary.buffer, &edits, &primary.view->carets, KILL_SELECTION); EndEdit(primary.buffer, &edits, &primary.view->carets, EndEdit_KillSelection);
} RegisterCommand(CMD_FormatSelection, "", ""); } RegisterCommand(CMD_FormatSelection, "", "");
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() {
For(Buffers) {
// 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);
}
}
} 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 CMD_KillProcess() { void CMD_KillProcess() {
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
KillProcess(main.view); KillProcess(main.view);
} RegisterCommand(CMD_KillProcess, "", "Kill process in the last active primary window"); } RegisterCommand(CMD_KillProcess, "", "Kill process in the last active primary window");
void CO_Rename(mco_coro *co) {
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(CoCurr->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) {
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(CoCurr->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);
String buffer_name = main.buffer->name;
DeleteFile(main.buffer->name);
Close(main.buffer->id);
} RegisterCommand(CMD_DeleteFile, "", "Close the open buffer and delete it's corresponding file on disk");
// 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) {
BSet main = GetBSet(PrimaryWindowID);
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->dirty) {
continue;
}
if (it->temp || it->dont_try_to_save_in_bulk_ops) {
continue;
}
String question = Format(CoCurr->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;
}
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) {
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 = 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(CoCurr->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");
void CMD_MakeFontLarger() { void CMD_MakeFontLarger() {
FontSize += 1; FontSize += 1;
ReloadFont(PathToFont, (U32)FontSize); ReloadFont(PathToFont, (U32)FontSize);
CenterPrimaryViews();
} RegisterCommand(CMD_MakeFontLarger, "ctrl-equals", "Increase the font size"); } RegisterCommand(CMD_MakeFontLarger, "ctrl-equals", "Increase the font size");
void CMD_MakeFontSmaller() { void CMD_MakeFontSmaller() {
if (FontSize > 4) { if (FontSize > 4) {
FontSize -= 1; FontSize -= 1;
ReloadFont(PathToFont, (U32)FontSize); ReloadFont(PathToFont, (U32)FontSize);
CenterPrimaryViews();
} }
} RegisterCommand(CMD_MakeFontSmaller, "ctrl-minus", "Decrease the font size"); } RegisterCommand(CMD_MakeFontSmaller, "ctrl-minus", "Decrease the font size");
@@ -461,6 +201,59 @@ void CMD_ToggleFullscreen() {
} RegisterCommand(CMD_ToggleFullscreen, "f11", "Switches between the fullscreen and non-fulscreen mode"); } RegisterCommand(CMD_ToggleFullscreen, "f11", "Switches between the fullscreen and non-fulscreen mode");
void CMD_OpenLogs() { void CMD_OpenLogs() {
ErrorCount = 0;
Open(LogBuffer->name);
} RegisterCommand(CMD_OpenLogs, "", "Opens the text editor logs and clear error counter");
void CMD_OpenScratch() {
ErrorCount = 0;
Buffer *buffer = GetBuffer(NullBufferID); Buffer *buffer = GetBuffer(NullBufferID);
Open(buffer->name); Open(buffer->name);
} RegisterCommand(CMD_OpenLogs, "", "Opens the text editor logs"); } RegisterCommand(CMD_OpenScratch, "", "Opens the scratch buffer");
void CMD_Errors() {
CMD_OpenLogs();
} RegisterCommand(CMD_Errors, "", "Opens the text editor logs and clear error counter");
void CheckKeybindingColission() {
ForItem (x, GlobalCommands) {
if (x.trigger == NULL) continue;
ForItem (y, GlobalCommands) {
if (y.trigger == NULL) continue;
if (&x == &y) continue;
if (x.trigger == y.trigger) {
ReportErrorf("Hotkey colission between: '%S' and '%S'", x.name, y.name);
}
}
}
}
Range EncloseScope(Buffer *buffer, Int pos_min, Int pos_max, String16 open, String16 close) {
Range result = {pos_min, pos_max};
String16 buffer_string = GetString(buffer);
for (Int i = pos_min - 1; i >= 0; i -= 1) {
String16 string = Skip(buffer_string, i);
if (StartsWith(string, open)) {
result.min = i + open.len;
break;
}
}
for (Int i = pos_max; i < buffer->len; i += 1) {
String16 string = Skip(buffer_string, i);
if (StartsWith(string, close)) {
result.max = i;
break;
}
}
return result;
}
void CMD_SelectComment() {
BSet active = GetBSet(ActiveWindowID);
For (active.view->carets) {
Range scope = EncloseScope(active.buffer, it.range.min, it.range.max, u"/*", u"*/");
it.range = scope;
}
MergeCarets(active.buffer, &active.view->carets);
} RegisterCommand(CMD_SelectComment, "ctrl-shift-l", "Find /* and */ and select the content in between");

View File

@@ -41,8 +41,12 @@ void ClipboardCopy(View *view) {
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;
Range line_range = GetLineRange(buffer, line, &eof);
it.range = line_range; 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,11 +84,11 @@ 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() {

View File

@@ -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,
@@ -34,35 +25,7 @@ struct CachedTrigger {
}; };
Array<CachedTrigger> CachedTriggers; Array<CachedTrigger> CachedTriggers;
void Advance(Lexer *lex) { void EatWhitespaceEx(Lexer *lex) {
if (lex->at < lex->end) {
if (lex->at[0] == '\n') {
lex->line += 1;
lex->column = 0;
} else {
lex->column += 1;
}
lex->at += 1;
}
}
void Advance(Lexer *lex, int n) {
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);
} }
@@ -80,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")) {
@@ -118,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;
} }
@@ -138,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;
} }
@@ -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 == '|');

View File

@@ -1,44 +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) {
CoDestroy(&it); DestroyCoroutine(&it);
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;) {
@@ -48,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 = &it; _CoroutineContext = &it;
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;
@@ -69,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);

View 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);

View File

@@ -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,12 +208,11 @@ void DrawWindow(Window *window, Event &event) {
} }
// Underline word under mouse cursor // Underline word under mouse cursor
if (1) {
Caret caret = view->carets[0]; Caret caret = view->carets[0];
Vec2I mouse = MouseVec2I(); Vec2I mouse = MouseVec2I();
bool mouse_in_document = AreOverlapping(mouse, window->document_rect); bool mouse_in_document = AreOverlapping(mouse, window->document_rect);
if (mouse_in_document) { if (mouse_in_document) {
View *view = GetView(window->active_view);
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);
@@ -213,8 +220,10 @@ void DrawWindow(Window *window, Event &event) {
DrawUnderline(window, view, buffer, range, MouseUnderlineColor, 2); DrawUnderline(window, view, buffer, range, MouseUnderlineColor, 2);
} }
} }
}
if (event.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);

View File

@@ -347,11 +347,18 @@ struct { String string; SDL_Keycode value; } SDLKeycodeConversionTable[] = {
}; };
void FillEventWithBasicData(Event *event) { void FillEventWithBasicData(Event *event) {
#if OS_WINDOWS
if (GetKeyState(VK_SHIFT) & 0x8000) event->shift = 1;
if (GetKeyState(VK_CONTROL) & 0x8000) event->ctrl = 1;
if (GetKeyState(VK_MENU) & 0x8000) event->alt = 1;
if (GetKeyState(VK_LWIN) & 0x8000) event->super = 1;
#else
SDL_Keymod mod = SDL_GetModState(); SDL_Keymod mod = SDL_GetModState();
event->shift = (mod & SDL_KMOD_SHIFT) != 0; event->shift = (mod & SDL_KMOD_SHIFT) != 0;
event->ctrl = (mod & SDL_KMOD_CTRL) != 0; event->ctrl = (mod & SDL_KMOD_CTRL) != 0;
event->alt = (mod & SDL_KMOD_ALT) != 0; event->alt = (mod & SDL_KMOD_ALT) != 0;
event->super = (mod & SDL_KMOD_GUI) != 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,35 +480,32 @@ 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;
} }

View File

@@ -1,33 +1,8 @@
String16 FetchFuzzyViewLoadLine(View *view) { // float NewFuzzyRate(String16 s, String16 p) {
Buffer *buffer = GetBuffer(view->active_buffer); // float score = 0;
Range range = view->carets[0].range; // // try to do this: https://github.com/junegunn/fzf/blob/master/src/algo/algo.go
String16 string = GetString(buffer, range); // return score;
if (GetSize(range) == 0) { // }
Int line = PosToLine(buffer, range.min);
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 OpenCommand(BSet active) {
String16 string = FetchFuzzyViewLoadLine(active.view);
Open(string);
}
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 FuzzyRate(String16 s, String16 p) {
float score = 0; float score = 0;
@@ -98,6 +73,7 @@ Array<FuzzyPair> FuzzySearchLines(Allocator allocator, Buffer *buffer, Int line_
} }
void UpdateFuzzySearchView() { void UpdateFuzzySearchView() {
ProfileFunction();
Scratch scratch; Scratch scratch;
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
String16 line_string = GetLineStringWithoutNL(active.buffer, 0); String16 line_string = GetLineStringWithoutNL(active.buffer, 0);
@@ -123,13 +99,62 @@ void UpdateFuzzySearchView() {
} }
} }
void SetFuzzy(View *view) { String16 FetchFuzzyViewLoadLine(View *view) {
AddCommand(&view->commands, "Open", OpenKeySet, [](){ 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 active = GetBSet(ActiveWindowID);
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
NextActiveWindowID = main.window->id; NextActiveWindowID = main.window->id;
String16 string = FetchFuzzyViewLoadLine(active.view); 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); Open(string);
}); }
void SetFuzzy(View *view) {
AddCommand(&view->commands, "Open", OpenKeySet, CMD_OpenForFuzzyView);
view->update_hook = UpdateFuzzySearchView; view->update_hook = UpdateFuzzySearchView;
} }

View File

@@ -9,9 +9,9 @@ bool RunGCThisFrame;
bool SearchCaseSensitive = false; bool SearchCaseSensitive = false;
bool SearchWordBoundary = false; bool SearchWordBoundary = false;
bool BreakOnError = false; bool BreakOnError = false;
Int ErrorCount;
Allocator SysAllocator = {SystemAllocatorProc}; Allocator SysAllocator = {SystemAllocatorProc};
String ConfigDir;
float DPIScale = 1.0f; float DPIScale = 1.0f;
// @WARNING: be careful about using this, should only be used for debugging // @WARNING: be careful about using this, should only be used for debugging
@@ -28,7 +28,10 @@ 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;
@@ -92,8 +95,14 @@ 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};
@@ -157,7 +166,6 @@ 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, PathToFont, ""); RegisterVariable(String, PathToFont, "");
RegisterVariable(Float, UndoMergeTime, 0.3); RegisterVariable(Float, UndoMergeTime, 0.3);
@@ -166,10 +174,23 @@ 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 // PROJECT_MANAGEMENT
// Set at the beginning of the program to current directory // Set at the beginning of the program to current directory
RegisterVariable(String, ProjectDirectory, ""); 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 // PLUGIN_LOAD_VCVARS
RegisterVariable(String, VCVarsPath, "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvars64.bat"); RegisterVariable(String, VCVarsPath, "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvars64.bat");

View File

@@ -5,7 +5,7 @@ BufferID BuildBufferID;
void InitBuildWindow() { void InitBuildWindow() {
Window *window = CreateWind(); Window *window = CreateWind();
BuildWindowID = window->id; BuildWindowID = window->id;
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectDirectory, "build")); Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectFolder, "build"));
buffer->special = true; buffer->special = true;
buffer->no_history = true; buffer->no_history = true;
BuildBufferID = buffer->id; BuildBufferID = buffer->id;
@@ -22,6 +22,7 @@ void InitBuildWindow() {
} }
void LayoutBuildWindow(Rect2I *rect, int16_t wx, int16_t wy) { void LayoutBuildWindow(Rect2I *rect, int16_t wx, int16_t wy) {
Unused(wx); Unused(wy);
Window *window = GetWindow(BuildWindowID); Window *window = GetWindow(BuildWindowID);
Rect2I copy_rect = *rect; Rect2I copy_rect = *rect;
if (!window->visible) { if (!window->visible) {
@@ -31,42 +32,56 @@ void LayoutBuildWindow(Rect2I *rect, int16_t wx, int16_t wy) {
window->document_rect = window->total_rect = CutBottom(rect, barsize); window->document_rect = window->total_rect = CutBottom(rect, barsize);
} }
BSet ExecBuild(String cmd, String working_dir = ProjectDirectory) { BSet ExecBuild(String windows_cmd, String unix_cmd, String working_dir = ProjectFolder) {
SaveAll();
Scratch scratch;
BSet build = GetBSet(BuildWindowID); BSet build = GetBSet(BuildWindowID);
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
SelectRange(build.view, Range{}); SelectRange(build.view, Range{});
ResetBuffer(build.buffer); ResetBuffer(build.buffer);
Exec(build.view->id, true, cmd, working_dir); {
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->active_goto_list = build.view->id;
main.window->goto_list_pos = 0; main.window->goto_list_pos = 0;
build.window->visible = true;
return build; return build;
} }
void CMD_Build() { void CMD_Build1() {
CMD_SaveAll(); ExecBuild(Build1OnWindows, Build1OnUnix);
#if OS_WINDOWS } RegisterCommand(CMD_Build1, "ctrl-b", "Run Build1OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer");
ExecBuild("build.bat");
#else void CMD_Build2() {
ExecBuild("sh build.sh"); ExecBuild(Build2OnWindows, Build2OnUnix);
#endif } RegisterCommand(CMD_Build2, "alt-b", "Run Build2OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer");
BSet main = GetBSet(BuildWindowID);
main.window->visible = true; void CMD_Build3() {
} RegisterCommand(CMD_Build, "f1", "Run build.sh or build.bat in working directory, output is printed in a popup console and a special build buffer"); 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() { void CMD_RunFile() {
Scratch scratch; Scratch scratch;
BSet primary = GetBSet(PrimaryWindowID); BSet primary = GetBSet(PrimaryWindowID);
BSet build = GetBSet(BuildWindowID); // @todo: this calls for language_cpp, language_c, language_python etc.
SaveBuffer(primary.buffer); String windows_command = "";
String unix_command = "";
if (OS_WINDOWS) { if (EndsWith(primary.buffer->name, ".py")) {
String cmd = Format(scratch, "cl %S -Fe:cfile.exe /Zi /FC /nologo /WX /W3 /wd4200 /wd4334 /diagnostics:column && cfile.exe", primary.buffer->name); unix_command = windows_command = Format(scratch, "python %S", primary.buffer->name);
ExecBuild(cmd, GetDirectory(primary.buffer));
} else { } else {
String cmd = 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\nEOF", primary.buffer->name); 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);
ExecBuild(cmd, GetDirectory(primary.buffer)); 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);
} }
build.window->visible = true; ExecBuild(windows_command, unix_command, GetDirectory(primary.buffer));
} RegisterCommand(CMD_RunFile, "", "Run and build current file, currently only C/C++ supported"); } RegisterCommand(CMD_RunFile, "", "Run and build current file, currently only C/C++ supported");
void CMD_ShowBuildWindow() { void CMD_ShowBuildWindow() {

View File

@@ -1,37 +1,21 @@
WindowID CommandWindowID; WindowID CommandWindowID;
void CMD_ShowCommands() { void CMD_ShowCommands() {
// @todo: maybe redo this, similar behavior but use View stored information
// if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == CMD_ShowCommands) {
// NextActiveWindowID = PrimaryWindowID;
// return;
// }
BSet command_bar = GetBSet(CommandWindowID); BSet command_bar = GetBSet(CommandWindowID);
command_bar.window->visible = true; command_bar.window->visible = true;
NextActiveWindowID = command_bar.window->id; NextActiveWindowID = command_bar.window->id;
ResetBuffer(command_bar.buffer); ResetBuffer(command_bar.buffer);
For (GlobalCommands) { For (GlobalCommands) {
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); RawAppendf(command_bar.buffer, "\n:%-30S <|| ", it.name);
if (it.docs.len) { if (it.docs.len) {
RawAppendf(command_bar.buffer, "%S", it.docs); RawAppendf(command_bar.buffer, "%S", it.docs);
} }
} }
command_bar.view->update_scroll = true; command_bar.view->update_scroll = true;
SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer)); SelectRange(command_bar.view, Range{});
} RegisterCommand(CMD_ShowCommands, "ctrl-shift-p", "List available commands and their documentation inside the command window"); } RegisterCommand(CMD_ShowCommands, "ctrl-shift-p", "List available commands and their documentation inside the command window");
void CMD_ShowDebugBufferList() { void CMD_ShowDebugBufferList() {
// @todo: maybe redo this, similar behavior but use View stored information
// if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == CMD_ShowDebugBufferList) {
// NextActiveWindowID = PrimaryWindowID;
// return;
// }
BSet command_bar = GetBSet(CommandWindowID); BSet command_bar = GetBSet(CommandWindowID);
command_bar.window->visible = true; command_bar.window->visible = true;
NextActiveWindowID = command_bar.window->id; NextActiveWindowID = command_bar.window->id;
@@ -47,15 +31,10 @@ void CMD_ShowDebugBufferList() {
RawAppendf(command_bar.buffer, "\n%S", it->name); RawAppendf(command_bar.buffer, "\n%S", it->name);
} }
command_bar.view->update_scroll = true; command_bar.view->update_scroll = true;
SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer)); 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"); } RegisterCommand(CMD_ShowDebugBufferList, "ctrl-shift-alt-p", "Show full list of buffers, including the special ones that normally just clutter list");
void CMD_ShowBufferList() { void CMD_ShowBufferList() {
// @todo: maybe redo this, similar behavior but use View stored information
// if (ActiveWindowID == CommandWindowID && LastExecutedManualCommand == CMD_ShowBufferList) {
// NextActiveWindowID = PrimaryWindowID;
// return;
// }
BSet command_bar = GetBSet(CommandWindowID); BSet command_bar = GetBSet(CommandWindowID);
command_bar.window->visible = true; command_bar.window->visible = true;
NextActiveWindowID = command_bar.window->id; NextActiveWindowID = command_bar.window->id;
@@ -71,10 +50,11 @@ void CMD_ShowBufferList() {
RawAppendf(command_bar.buffer, "\n%S", it->name); RawAppendf(command_bar.buffer, "\n%S", it->name);
} }
command_bar.view->update_scroll = true; command_bar.view->update_scroll = true;
SelectRange(command_bar.view, GetBufferBeginAsRange(command_bar.buffer)); SelectRange(command_bar.view, Range{});
} RegisterCommand(CMD_ShowBufferList, "ctrl-p", "List open buffers inside the command window that you can fuzzy search over"); } 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) { void LayoutCommandWindow(Rect2I *rect, int16_t wx, int16_t wy) {
Unused(wy);
Window *window = GetWindow(CommandWindowID); Window *window = GetWindow(CommandWindowID);
Rect2I copy_rect = *rect; Rect2I copy_rect = *rect;
if (!window->visible) { if (!window->visible) {
@@ -87,7 +67,7 @@ void LayoutCommandWindow(Rect2I *rect, int16_t wx, int16_t wy) {
void InitCommandWindow() { void InitCommandWindow() {
Window *window = CreateWind(); Window *window = CreateWind();
CommandWindowID = window->id; CommandWindowID = window->id;
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectDirectory, "command_bar")); Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectFolder, "command_bar"));
buffer->special = true; buffer->special = true;
buffer->no_history = true; buffer->no_history = true;
View *view = CreateView(buffer->id); View *view = CreateView(buffer->id);

View File

@@ -59,20 +59,19 @@ void CMD_EvalCommandsLineByLine() {
BufferID LoadConfig(String config_path) { BufferID LoadConfig(String config_path) {
ReportConsolef("Loading config %S...", config_path); ReportConsolef("Loading config %S...", config_path);
Window *window = GetWindow(NullWindowID); Window *window = GetWindow(NullWindowID);
ViewID active_view_before = window->active_view;
View *view = WindowOpenBufferView(window, config_path); View *view = WindowOpenBufferView(window, config_path);
Buffer *buffer = GetBuffer(view->active_buffer); Buffer *buffer = GetBuffer(view->active_buffer);
buffer->special = true;
EvalCommandsLineByLine({window, view, buffer}); EvalCommandsLineByLine({window, view, buffer});
if (window->active_view == view->id) { if (window->active_view == view->id) {
window->active_view = NullViewID; window->active_view = active_view_before;
} }
return buffer->id; return buffer->id;
} }
#define ExpectP(x, ...) \ #define ExpectP(x, ...) \
if (!(x)) { \ if (!(x)) { \
ReportErrorf("Failed to parse '" __FUNCTION__ "' command, " __VA_ARGS__); \ ReportErrorf("Failed to parse command " __VA_ARGS__); \
return; \ return; \
} }
@@ -80,14 +79,7 @@ void Set(String string) {
String name = SkipIdent(&string); String name = SkipIdent(&string);
ExpectP(name.len != 0, "expected a variable name, instead got '%S'", string); ExpectP(name.len != 0, "expected a variable name, instead got '%S'", string);
Variable *var = NULL; Variable *var = GetVariable(name);
For (Variables) {
if (name == it.name) {
var = &it;
break;
}
}
if (var) { if (var) {
SkipWhitespace(&string); SkipWhitespace(&string);
if (var->type == VariableType_String) { if (var->type == VariableType_String) {
@@ -125,8 +117,8 @@ void Set(String string) {
} }
#if PLUGIN_PROJECT_MANAGEMENT #if PLUGIN_PROJECT_MANAGEMENT
if (name == "ProjectDirectory") { if (name == "ProjectFolder") {
SetProjectDirectory(*var->string); SetProjectFolder(*var->string);
} }
#endif #endif
return; return;
@@ -153,6 +145,7 @@ void Set(String string) {
if (trigger) { if (trigger) {
cmd->binding = quote; cmd->binding = quote;
cmd->trigger = trigger; cmd->trigger = trigger;
CheckKeybindingColission();
} }
return; return;
} }

View File

@@ -1,3 +1,4 @@
#if PLUGIN_CONFIG #if PLUGIN_CONFIG
void Set(String string); void Set(String string);
String InsertVariables(Allocator allocator, String string);
#endif #endif

View File

@@ -12,7 +12,7 @@ void InitDebugWindow() {
window->primary = false; window->primary = false;
window->jump_history = false; window->jump_history = false;
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectDirectory, "debug")); Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectFolder, "debug"));
DebugBufferID = buffer->id; DebugBufferID = buffer->id;
buffer->no_history = true; buffer->no_history = true;
buffer->special = true; buffer->special = true;
@@ -25,6 +25,7 @@ void InitDebugWindow() {
} }
void LayoutDebugWindow(Rect2I *rect, int16_t wx, int16_t wy) { void LayoutDebugWindow(Rect2I *rect, int16_t wx, int16_t wy) {
Unused(rect);
Window *window = GetWindow(DebugWindowID); Window *window = GetWindow(DebugWindowID);
Rect2 screen_rect = Rect0Size((float)wx, (float)wy); Rect2 screen_rect = Rect0Size((float)wx, (float)wy);
Vec2 size = GetSize(screen_rect); Vec2 size = GetSize(screen_rect);
@@ -47,9 +48,7 @@ void UpdateDebugWindow() {
return; return;
} }
BSet main = GetBSet(PrimaryWindowID);
RawReplaceText(set.buffer, GetRange(set.buffer), u"Active buffers and views:\n"); RawReplaceText(set.buffer, GetRange(set.buffer), u"Active buffers and views:\n");
For (Views) { For (Views) {
Buffer *buffer = GetBuffer(it->active_buffer); 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); 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);

View File

@@ -13,16 +13,55 @@ void InsertDirectoryNavigation(Buffer *buffer) {
Scratch scratch; Scratch scratch;
ResetBuffer(buffer); ResetBuffer(buffer);
RawAppendf(buffer, "\n"); RawAppendf(buffer, "\n");
RawAppendf(buffer, "..\n");
for (FileIter it = IterateFiles(scratch, buffer->name); IsValid(it); Advance(&it)) { for (FileIter it = IterateFiles(scratch, buffer->name); IsValid(it); Advance(&it)) {
RawAppendf(buffer, "%S\n", it.filename); 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) { void OpenDirectoryNavigation(View *view) {
SetFuzzy(view); SetFuzzy(view);
// view->update_hook = UpdateDirectoryNavigation;
Buffer *buffer = GetBuffer(view->active_buffer); Buffer *buffer = GetBuffer(view->active_buffer);
InsertDirectoryNavigation(buffer); InsertDirectoryNavigation(buffer);
SelectRange(view, GetBufferBeginAsRange(buffer)); SelectRange(view, Range{});
} }
#if 0 #if 0

View 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

View File

@@ -2,16 +2,19 @@ void Windows_SetupVCVarsall(mco_coro *co) {
View *view = NULL; View *view = NULL;
{ {
Scratch scratch; Scratch scratch;
String working_dir = ProjectDirectory; String working_dir = ProjectFolder;
String buffer_name = GetUniqueBufferName(working_dir, "vcvarsall-"); String buffer_name = GetUniqueBufferName(working_dir, "vcvarsall-");
String cmd = Format(scratch, "\"%S\" && set", VCVarsPath); String cmd = Format(scratch, "\"%S\" && set", VCVarsPath);
view = ExecHidden(buffer_name, cmd, working_dir); 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 (;;) { for (;;) {
if (!ProcessIsActive(view->id)) { if (!ProcessIsActive(view->id)) {
break; break;
} }
CoYield(co); Yield(co);
} }
Scratch scratch; Scratch scratch;
@@ -19,6 +22,7 @@ void Windows_SetupVCVarsall(mco_coro *co) {
String16 string16 = GetString(buffer); String16 string16 = GetString(buffer);
String string = ToString(scratch, string16); String string = ToString(scratch, string16);
Array<String> lines = Split(scratch, string, "\n"); Array<String> lines = Split(scratch, string, "\n");
ProcessEnviroment.len = 0;
For (lines) { For (lines) {
String s = Trim(it); String s = Trim(it);
Array<String> values = Split(scratch, s, "="); Array<String> values = Split(scratch, s, "=");
@@ -27,11 +31,9 @@ void Windows_SetupVCVarsall(mco_coro *co) {
} }
} }
void LoadVCVars() { void LoadVCVars() {
CoRemove("Windows_SetupVCVarsall"); RemoveCoroutine("Windows_SetupVCVarsall");
CoData *co_data = CoAdd(Windows_SetupVCVarsall); CCtx *co_data = AddCoroutine(Windows_SetupVCVarsall);
co_data->dont_wait_until_resolved = true; co_data->dont_wait_until_resolved = true;
CoResume(co_data); ResumeCoroutine(co_data);
} }

View 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

View File

@@ -1,4 +1,4 @@
#if BUILD_DEBUG #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,60 +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())
struct ProfileScopeClass {
#endif // SPALL_H ProfileScopeClass(const char *name, int len) { BeginProfileScopeEx(name, len); }
~ProfileScopeClass() { EndProfileScope(); }
};
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 ProfileScopeEx(name) ProfileScopeClass PROFILE_SCOPE_VAR_((name).data, (int)(name).len)
#define ProfileScope(name) ProfileScopeClass PROFILE_SCOPE_VAR_##name(#name, sizeof(#name) - 1) #define ProfileScope(name) ProfileScopeClass PROFILE_SCOPE_VAR_##name(#name, sizeof(#name) - 1)
#define ProfileFunction() ProfileScopeClass PROFILE_SCOPE_FUNCTION(__FUNCTION__, sizeof(__FUNCTION__) - 1) #define ProfileFunction() ProfileScopeClass PROFILE_SCOPE_FUNCTION(__FUNCTION__, sizeof(__FUNCTION__) - 1)
struct ProfileScopeClass { SPALL_FN void BeginProfiler();
ProfileScopeClass(const char *name, int len) { _BeginProfileScope(name, len); } SPALL_FN void EndProfiler();
~ProfileScopeClass() { EndProfileScope(); }
};
#else #else
#define ProfileScopeEx(name) #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

View File

@@ -1,5 +1,5 @@
void SetProjectDirectory(String dir) { void SetProjectFolder(String dir) {
ProjectDirectory = Intern(&GlobalInternTable, dir); ProjectFolder = Intern(&GlobalInternTable, dir);
Scratch scratch; Scratch scratch;
For (Buffers) { For (Buffers) {
if (it->special) { if (it->special) {
@@ -9,18 +9,14 @@ void SetProjectDirectory(String dir) {
} }
} }
void CMD_SetProjectDirectoryHere() {
BSet main = GetBSet(PrimaryWindowID);
SetProjectDirectory(GetDirectory(main.buffer));
} RegisterCommand(CMD_SetProjectDirectoryHere, "", "Sets work directory to the directory of the current buffer, it also renames couple special buffers to make them accomodate the new ProjectDirectory");
void CO_OpenCode(mco_coro *co) { void CO_OpenCode(mco_coro *co) {
Array<String> patterns = SplitWhitespace(CoCurr->arena, OpenCodePatterns); CCtx *ctx = GetCoroutineContext();
Array<String> exclude_patterns = SplitWhitespace(CoCurr->arena, OpenCodeExcludePatterns); Array<String> patterns = SplitWhitespace(ctx->arena, OpenCodePatterns);
Array<String> dirs = {CoCurr->arena}; Array<String> exclude_patterns = SplitWhitespace(ctx->arena, OpenCodeExcludePatterns);
Add(&dirs, Copy(CoCurr->arena, ProjectDirectory)); Array<String> dirs = {ctx->arena};
Add(&dirs, Copy(ctx->arena, ProjectFolder));
for (int diri = 0; diri < dirs.len; diri += 1) { for (int diri = 0; diri < dirs.len; diri += 1) {
for (FileIter it = IterateFiles(CoCurr->arena, dirs[diri]); IsValid(it); Advance(&it)) { for (FileIter it = IterateFiles(ctx->arena, dirs[diri]); IsValid(it); Advance(&it)) {
bool should_open = true; bool should_open = true;
if (!it.is_directory) { if (!it.is_directory) {
should_open = false; should_open = false;
@@ -50,12 +46,92 @@ void CO_OpenCode(mco_coro *co) {
BufferOpenFile(it.absolute_path); BufferOpenFile(it.absolute_path);
} }
CoYield(co); Yield(co);
} }
} }
} RegisterCoroutineCommand( } RegisterCoroutineCommand(
CO_OpenCode, CO_OpenCode,
"", "",
"Open all code files in current ProjectDirectory, the code files are determined through NonCodePatterns_EndsWith config variable list", "Open all code files in current ProjectFolder, the code files are determined through NonCodePatterns_EndsWith config variable list",
data->dont_wait_until_resolved = true; 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.");

View File

@@ -1 +1 @@
void SetProjectDirectory(String name); void SetProjectFolder(String name);

View File

@@ -2097,7 +2097,9 @@ uint8_t command_buf[COMMAND_BUF_SIZE];
uint8_t reply_buf[REPLY_BUF_SIZE]; uint8_t reply_buf[REPLY_BUF_SIZE];
ClientContext RDBG_Ctx; ClientContext RDBG_Ctx;
bool RDBG_InitConnection(mco_coro *co) { bool RDBG_InitConnection(mco_coro *co, bool create_session = true) {
CCtx *ctx = GetCoroutineContext();
ReportConsolef("Remedybg: InitConnection");
enum rdbg_CommandResult res; enum rdbg_CommandResult res;
if (RDBG_Ctx.command_pipe_handle != NULL) { if (RDBG_Ctx.command_pipe_handle != NULL) {
enum rdbg_TargetState state; enum rdbg_TargetState state;
@@ -2108,21 +2110,40 @@ bool RDBG_InitConnection(mco_coro *co) {
} }
String file = ""; String file = "";
if (create_session) {
if (BinaryUnderDebug.len) { if (BinaryUnderDebug.len) {
Window *window = GetWindow(PrimaryWindowID); Window *window = GetWindow(PrimaryWindowID);
ResolvedOpen res = ResolveOpen(CoCurr->arena, window, BinaryUnderDebug, ResolveOpenMeta_DontError | ResolveOpenMeta_DontExec); ResolvedOpen res = ResolveOpen(ctx->arena, window, BinaryUnderDebug, ResolveOpenMeta_DontError | ResolveOpenMeta_DontExec);
if (res.kind == OpenKind_Goto) { if (res.kind == OpenKind_Goto) {
file = Intern(&GlobalInternTable, res.path); file = Intern(&GlobalInternTable, res.path);
} else {
ReportErrorf("Failed to find the executable pointer by BinaryUnderDebug: %S", BinaryUnderDebug);
return false;
} }
} }
if (file.len == 0) { if (file.len == 0) {
file = QueryUserFile(co); Scratch scratch;
for (FileIter it = IterateFiles(scratch, ProjectFolder); IsValid(it); Advance(&it)) {
if (EndsWith(it.filename, ".rdbg")) {
file = Intern(&GlobalInternTable, it.absolute_path);
break;
}
}
} }
String session_name = Format(CoCurr->arena, "te%llu", GetTimeNanos()); if (file.len == 0) {
String remedy_string = Format(CoCurr->arena, "%S --servername %S", RemedyBGPath, session_name); ReportErrorf("Couldn't find neither .rdbg file, nor use the BinaryUnderDebug variable to locate the binary");
Exec(NullViewID, true, remedy_string, GetPrimaryDirectory()); return false;
}
}
String session_name = Format(ctx->arena, "te%llu", GetTimeNanos());
String remedy_string = Format(ctx->arena, "%S --servername %S", RemedyBGPath, session_name);
ReportConsolef("Remedybg: %S", remedy_string);
ExecArgs args = ShellArgs(ctx->arena, LogView->id, remedy_string, GetPrimaryDirectory());
args.scroll_to_end = true;
Exec(args);
MemoryZero(&RDBG_Ctx, sizeof(RDBG_Ctx)); MemoryZero(&RDBG_Ctx, sizeof(RDBG_Ctx));
RDBG_Ctx.cmd.data = command_buf; RDBG_Ctx.cmd.data = command_buf;
RDBG_Ctx.cmd.capacity = sizeof(command_buf); RDBG_Ctx.cmd.capacity = sizeof(command_buf);
@@ -2148,10 +2169,16 @@ bool RDBG_InitConnection(mco_coro *co) {
} }
MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error));
if (create_session) {
if (EndsWith(file, ".rdbg")) {
ReportConsolef("Remedybg: OpenSession %S", file);
OpenSession(&RDBG_Ctx, file.data, &res);
} else {
ReportConsolef("Remedybg: CreateSession %S", file);
rdbg_Id cfg_id; rdbg_Id cfg_id;
char *exe = file.data; char *exe = file.data;
char *args = NULL; char *args = NULL;
char *work_dir = ProjectDirectory.data; char *work_dir = ProjectFolder.data;
char *env = NULL; char *env = NULL;
AddSessionConfig(&RDBG_Ctx, exe, args, work_dir, env, true, true, &res, &cfg_id); AddSessionConfig(&RDBG_Ctx, exe, args, work_dir, env, true, true, &res, &cfg_id);
if (ContextHadError(&RDBG_Ctx)) { if (ContextHadError(&RDBG_Ctx)) {
@@ -2166,6 +2193,8 @@ bool RDBG_InitConnection(mco_coro *co) {
MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error)); MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error));
return true; return true;
} }
}
}
return true; return true;
} }
@@ -2241,7 +2270,8 @@ void CO_StopDebugging(mco_coro *co) {
} RegisterCoroutineCommand(CO_StopDebugging, "shift-f5", "Stop debugging"); } RegisterCoroutineCommand(CO_StopDebugging, "shift-f5", "Stop debugging");
void CO_AddBreakpoint(mco_coro *co) { void CO_AddBreakpoint(mco_coro *co) {
if (!RDBG_InitConnection(co)) { bool conn = RDBG_InitConnection(co);
if (!conn) {
return; return;
} }
@@ -2257,6 +2287,20 @@ void CO_AddBreakpoint(mco_coro *co) {
} }
} RegisterCoroutineCommand(CO_AddBreakpoint, "f9", "Add a breakpoint at filename + line"); } RegisterCoroutineCommand(CO_AddBreakpoint, "f9", "Add a breakpoint at filename + line");
void CO_SelfAttachDebugger(mco_coro *co) {
bool conn = RDBG_InitConnection(co, false);
if (!conn) {
return;
}
enum rdbg_CommandResult res;
AttachToProcessById(&RDBG_Ctx, GetCurrentProcessId(), true, &res);
if (res != RDBG_COMMAND_RESULT_OK) {
ReportErrorf("Remedy error: %s", RDBG_Ctx.last_error);
MemoryZero(RDBG_Ctx.last_error, sizeof(RDBG_Ctx.last_error));
return;
}
} RegisterCoroutineCommand(CO_SelfAttachDebugger, "", "Spawn and self attach the debugger to the text editor");
void QuitDebugger() { void QuitDebugger() {
if (RDBG_Ctx.command_pipe_handle == NULL) { if (RDBG_Ctx.command_pipe_handle == NULL) {
return; return;

View File

@@ -4,9 +4,10 @@ struct SearchOpenBuffersParams {
}; };
void Coro_SearchOpenBuffers(mco_coro *co) { void Coro_SearchOpenBuffers(mco_coro *co) {
SearchOpenBuffersParams *param = (SearchOpenBuffersParams *)CoCurr->user_ctx; CCtx *ctx = GetCoroutineContext();
SearchOpenBuffersParams *param = (SearchOpenBuffersParams *)ctx->user_ctx;
Array<BufferID> buffers = {CoCurr->arena}; Array<BufferID> buffers = {ctx->arena};
For (Buffers) { For (Buffers) {
Add(&buffers, it->id); Add(&buffers, it->id);
} }
@@ -36,7 +37,7 @@ void Coro_SearchOpenBuffers(mco_coro *co) {
Appendf(out_view, "%S ||> %S:%lld:%lld\n", line_string8, it->name, (long long)line + 1, (long long)column + 1); Appendf(out_view, "%S ||> %S:%lld:%lld\n", line_string8, it->name, (long long)line + 1, (long long)column + 1);
} }
} }
CoYield(co); Yield(co);
} }
} }
@@ -48,19 +49,21 @@ void UpdateSearchOpenBuffersView() {
if (active.view->prev_search_line_hash != hash) { if (active.view->prev_search_line_hash != hash) {
active.view->prev_search_line_hash = hash; active.view->prev_search_line_hash = hash;
if (line_string.len > 0) { 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]; Caret caret = active.view->carets[0];
SelectEntireBuffer(active.view); SelectEntireBuffer(active.view);
Replace(active.view, line_string); Replace(active.view, line_string);
Append(active.view, "\n", false); Append(active.view, "\n", false);
CoRemove("Coro_SearchOpenBuffers"); RemoveCoroutine("Coro_SearchOpenBuffers");
CoData *dat = CoAdd(Coro_SearchOpenBuffers); CCtx *dat = AddCoroutine(Coro_SearchOpenBuffers);
SearchOpenBuffersParams *param = AllocType(dat->arena, SearchOpenBuffersParams); SearchOpenBuffersParams *param = AllocType(dat->arena, SearchOpenBuffersParams);
param->needle = Copy16(dat->arena, line_string); param->needle = Copy16(dat->arena, line_string);
param->view = active.view->id; param->view = active.view->id;
dat->user_ctx = param; dat->user_ctx = param;
dat->dont_wait_until_resolved = true; dat->dont_wait_until_resolved = true;
CoResume(dat); ResumeCoroutine(dat);
active.view->carets[0] = caret; active.view->carets[0] = caret;
} }
@@ -76,15 +79,8 @@ void CMD_SearchOpenBuffers() {
NextActiveWindowID = main.window->id; NextActiveWindowID = main.window->id;
JumpTempBuffer(&main); JumpTempBuffer(&main);
AddCommand(&main.view->commands, "Open", OpenKeySet, []() { main.buffer->no_history = true;
BSet active = GetBSet(ActiveWindowID); AddCommand(&main.view->commands, "Open", OpenKeySet, CMD_OpenAndSetGotoNext);
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);
});
main.view->update_hook = UpdateSearchOpenBuffersView; main.view->update_hook = UpdateSearchOpenBuffersView;
if (string.len) { if (string.len) {
SelectRange(main.view, GetLineRangeWithoutNL(main.buffer, 0)); SelectRange(main.view, GetLineRangeWithoutNL(main.buffer, 0));

View File

@@ -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() {
@@ -58,7 +63,7 @@ void CMD_ToggleSearchWordBoundary() {
void InitSearchWindow() { void InitSearchWindow() {
Window *window = CreateWind(); Window *window = CreateWind();
SearchWindowID = window->id; SearchWindowID = window->id;
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectDirectory, "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);
@@ -79,6 +84,7 @@ void InitSearchWindow() {
} }
void LayoutSearchWindow(Rect2I *rect, int16_t wx, int16_t wy) { void LayoutSearchWindow(Rect2I *rect, int16_t wx, int16_t wy) {
Unused(wx); Unused(wy);
Window *window = GetWindow(SearchWindowID); Window *window = GetWindow(SearchWindowID);
Rect2I copy_rect = *rect; Rect2I copy_rect = *rect;
if (!window->visible) { if (!window->visible) {
@@ -92,12 +98,15 @@ void LayoutSearchWindow(Rect2I *rect, int16_t wx, int16_t wy) {
void UpdateSearchWindow() { void UpdateSearchWindow() {
if (ActiveWindowID == SearchWindowID) { if (ActiveWindowID == SearchWindowID) {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
if (active.buffer->begin_frame_change_id != active.buffer->change_id) { if (SearchBufferChangeID != active.buffer->change_id) {
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
main.view->carets[0] = main.window->search_bar_anchor; main.view->carets[0] = main.window->search_bar_anchor;
String16 seek = GetString(active.buffer, GetRange(active.buffer)); String16 seek = GetString(active.buffer, GetRange(active.buffer));
Find(main.view, seek, true); Find(main.view, seek, true);
if (!IsMainCaretOnScreen(main.window).caret_on_screen) {
CenterView(main.window->id); CenterView(main.window->id);
} }
SearchBufferChangeID = active.buffer->change_id;
}
} }
} }

View File

@@ -3,7 +3,7 @@ WindowID StatusWindowID;
void InitStatusWindow() { void InitStatusWindow() {
Window *window = CreateWind(); Window *window = CreateWind();
StatusWindowID = window->id; StatusWindowID = window->id;
Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectDirectory, "status_bar")); Buffer *buffer = CreateBuffer(SysAllocator, GetUniqueBufferName(ProjectFolder, "status_bar"));
buffer->special = true; buffer->special = true;
View *view = CreateView(buffer->id); View *view = CreateView(buffer->id);
view->special = true; view->special = true;
@@ -18,6 +18,7 @@ void InitStatusWindow() {
} }
void LayoutStatusWindow(Rect2I *rect, int16_t wx, int16_t wy) { void LayoutStatusWindow(Rect2I *rect, int16_t wx, int16_t wy) {
Unused(wx); Unused(wy);
Window *window = GetWindow(StatusWindowID); Window *window = GetWindow(StatusWindowID);
Rect2I copy_rect = *rect; Rect2I copy_rect = *rect;
if (!window->visible) { if (!window->visible) {
@@ -29,60 +30,59 @@ void LayoutStatusWindow(Rect2I *rect, int16_t wx, int16_t wy) {
void UpdateStatusWindow() { void UpdateStatusWindow() {
ProfileFunction(); ProfileFunction();
Window *status_bar_window = GetWindow(StatusWindowID, NULL);
Scratch scratch; Scratch scratch;
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
BSet title = GetBSet(status_bar_window); BSet title = GetBSet(StatusWindowID);
title.view->scroll.y = 0; 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); String16 buffer_string = GetString(title.buffer);
Range replace_range = {0, title.buffer->len}; Range replace_range = {0, title.buffer->len};
bool found_separator = Seek(buffer_string, u" |", &replace_range.max); Seek(buffer_string, u"\n", &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]; Caret caret = main.view->carets[0];
XY xy = PosToXY(main.buffer, GetFront(caret)); XY xy = PosToXY(main.buffer, GetFront(caret));
// add separator at the end of buffer String indicators = "";
if (!found_separator) { if (main.buffer->dirty) {
SelectRange(title.view, GetBufferEndAsRange(title.buffer)); indicators = Format(scratch, "%S!", indicators);
ReplaceEx(scratch, title.view, u" | :Prev :Next :Close"); }
if (SearchCaseSensitive) {
indicators = Format(scratch, "%SC", indicators);
}
if (SearchWordBoundary) {
indicators = Format(scratch, "%SW", indicators);
} }
// replace data up to separator with filename and stuff String commands = Format(scratch, ":Errors[%d]", ErrorCount);
const char *reopen = main.buffer->changed_on_disk ? " :Reopen" : ""; if (main.buffer->changed_on_disk) {
const char *case_sens = SearchCaseSensitive ? " C" : ""; commands = Format(scratch, "%S :Reopen", commands);
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) { For (ActiveProcesses) {
if (it.view_id == main.view->id.id) { 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); s = Format(scratch, "%S %lld :KillProcess", s, (long long)it.id);
} }
} }

View File

@@ -49,6 +49,20 @@ void CMD_FocusWindow3() {
} }
} RegisterCommand(CMD_FocusWindow3, "ctrl-3", "Select the 3rd window, counting from left"); } 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() { void CMD_NewWindow() {
CreateWind(); CreateWind();
} RegisterCommand(CMD_NewWindow, "ctrl-backslash", "Creates a new window"); } RegisterCommand(CMD_NewWindow, "ctrl-backslash", "Creates a new window");

View 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");

View File

@@ -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
@@ -8,11 +461,11 @@ void UpdateProcesses() {
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)) {
ReportConsolef("process %lld exit code = %d", it.id, it.exit_code); ReportConsolef("process %lld exit code = %d", it.id, it.exit_code);
@@ -21,20 +474,23 @@ void UpdateProcesses() {
} }
} }
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 = {};
ReportConsolef("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) { args.output_view = output_view;
Add(&ActiveProcesses, process); 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 {
@@ -44,9 +500,14 @@ 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 = {}) {
ReportConsolef("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);
@@ -60,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 ...
} }
} }
@@ -73,27 +534,9 @@ 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;
} }
} }
return false; return false;
} }
View *ExecHidden(String buffer_name, String cmd, String working_dir) {
View *view = OpenBufferView(buffer_name);
Buffer *buffer = GetBuffer(view->active_buffer);
buffer->dont_try_to_save_in_bulk_ops = true;
Exec(view->id, true, cmd, working_dir);
return view;
}
BSet Exec(String cmd, String working_dir, bool set_active = true) {
BSet main = GetBSet(PrimaryWindowID);
if (set_active) {
NextActiveWindowID = main.window->id;
}
JumpTempBuffer(&main);
Exec(main.view->id, true, cmd, working_dir);
return main;
}

View File

@@ -1,6 +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"
@@ -33,6 +35,8 @@
#define PLUGIN_DIRECTORY_NAVIGATION 1 #define PLUGIN_DIRECTORY_NAVIGATION 1
#define PLUGIN_LOAD_VCVARS OS_WINDOWS #define PLUGIN_LOAD_VCVARS OS_WINDOWS
#define PLUGIN_REMEDYBG OS_WINDOWS #define PLUGIN_REMEDYBG OS_WINDOWS
#define PLUGIN_FILE_COMMANDS 1
#define PLUGIN_WORD_COMPLETE 1
#include "plugin_directory_navigation.h" #include "plugin_directory_navigation.h"
#include "plugin_search_window.h" #include "plugin_search_window.h"
@@ -41,6 +45,7 @@
#include "text_editor.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"
@@ -69,6 +74,9 @@
#include "plugin_record_events.cpp" #include "plugin_record_events.cpp"
#include "plugin_load_vcvars.cpp" #include "plugin_load_vcvars.cpp"
#include "plugin_remedybg.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), {
@@ -168,9 +176,9 @@ void ShowQuitAppUI(mco_coro *co) {
} }
void CMD_Quit() { void CMD_Quit() {
CoRemove("ShowQuitAppUI"); RemoveCoroutine("ShowQuitAppUI");
CoData *data = CoAdd(ShowQuitAppUI); CCtx *data = AddCoroutine(ShowQuitAppUI);
CoResume(data); ResumeCoroutine(data);
} RegisterCommand(CMD_Quit, "", "Ask user which files he would like to save and exit"); } RegisterCommand(CMD_Quit, "", "Ask user which files he would like to save and exit");
void OnCommand(Event event) { void OnCommand(Event event) {
@@ -362,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);
} }
@@ -401,9 +409,7 @@ 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->commands) { For (active.view->commands) {
if (it.trigger && MatchEvent(it.trigger, &event)) { if (it.trigger && MatchEvent(it.trigger, &event)) {
@@ -452,8 +458,11 @@ void OnCommand(Event event) {
} }
} }
IF_DEBUG(AssertRanges(main.view->carets)); #if SLOW_BUILD
IF_DEBUG(AssertRanges(active.view->carets)); BSet main = GetBSet(PrimaryWindowID);
AssertRanges(main.view->carets);
AssertRanges(active.view->carets);
#endif
} }
void EvalCommand(String command) { void EvalCommand(String command) {
@@ -506,7 +515,7 @@ void GarbageCollect() {
continue; continue;
} }
bool ref = ViewIsReferenced(it->id); bool ref = ViewIsReferenced(it);
if (ref) { if (ref) {
continue; continue;
} }
@@ -519,6 +528,7 @@ void GarbageCollect() {
#if PLUGIN_RECORD_GC #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"});
#endif #endif
Assert(it->special == false);
Dealloc(&it->commands); Dealloc(&it->commands);
Dealloc(it); Dealloc(it);
remove_item = true; remove_item = true;
@@ -536,7 +546,7 @@ void GarbageCollect() {
continue; continue;
} }
bool ref = BufferIsReferenced(it->id); bool ref = BufferIsReferenced(it);
if (ref) { if (ref) {
continue; continue;
} }
@@ -545,6 +555,7 @@ void GarbageCollect() {
#if PLUGIN_RECORD_GC #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 #endif
Assert(it->special == false);
remove_item = true; remove_item = true;
DeallocBuffer(it); DeallocBuffer(it);
} }
@@ -565,7 +576,7 @@ void GarbageCollect() {
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);
} }
@@ -626,6 +637,7 @@ void Update(Event event) {
ProfileFunction(); ProfileFunction();
LayoutWindows(event.xwindow, event.ywindow); LayoutWindows(event.xwindow, event.ywindow);
{
Scratch scratch; Scratch scratch;
Array<Window *> order = GetWindowZOrder(scratch); Array<Window *> order = GetWindowZOrder(scratch);
For(order) { For(order) {
@@ -636,6 +648,7 @@ void Update(Event event) {
view->main_caret_on_begin_frame = view->carets[0]; view->main_caret_on_begin_frame = view->carets[0];
view->update_scroll = true; view->update_scroll = true;
} }
}
For (Windows) { For (Windows) {
if (it->jump_history) { if (it->jump_history) {
@@ -667,22 +680,15 @@ void Update(Event event) {
} }
UpdateProcesses(); UpdateProcesses();
CoUpdate(&event); UpdateCoroutines(&event);
For(IterateInReverse(&order)) { 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) {
@@ -765,31 +771,22 @@ void Update(Event event) {
} }
} }
// Reopen modified buffers // IS THIS ENOUGH? Previously reopened everything
// @todo: This is O(n) so could see slow downs with number of buffers, likely need OS help to make it more For (Windows) {
// efficient BSet set = GetBSet(it);
For(Buffers) { TryReopeningWhenModified(set.buffer);
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);
}
}
}
} }
GarbageCollect(); GarbageCollect();
} }
Array<Event> FrameEvents;
void MainLoop() { void MainLoop() {
ProfileFunction(); ProfileFunction();
Scratch scratch;
FrameID += 1; FrameID += 1;
Array<Event> frame_events = GetEventsForFrame(scratch); FrameEvents.len = 0;
For(frame_events) { GetEventsForFrame(&FrameEvents);
For (FrameEvents) {
#if PLUGIN_RECORD_EVENTS #if PLUGIN_RECORD_EVENTS
if (it.kind != EVENT_UPDATE && !Testing) { if (it.kind != EVENT_UPDATE && !Testing) {
Serialize(EventBuffer, &it); Serialize(EventBuffer, &it);
@@ -823,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);
@@ -832,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);
{
Scratch scratch;
Array<Window *> order = GetWindowZOrder(scratch); Array<Window *> order = GetWindowZOrder(scratch);
For(IterateInReverse(&order)) { For(IterateInReverse(&order)) {
if (!it->visible) continue; if (!it->visible) continue;
DrawWindow(it, *GetLast(frame_events)); 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();
@@ -872,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
ProjectDirectory = 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;
@@ -914,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);
@@ -962,20 +980,27 @@ int main(int argc, char **argv)
{ {
Allocator sys_allocator = GetSystemAllocator(); Allocator sys_allocator = GetSystemAllocator();
Scratch scratch; Scratch scratch;
Buffer *null_buffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectDirectory, "logs", "")); Buffer *null_buffer = CreateBuffer(sys_allocator, Format(scratch, "%S/scratch", ProjectFolder));
null_buffer->special = true; null_buffer->special = true;
View *null_view = CreateView(null_buffer->id); View *null_view = CreateView(null_buffer->id);
null_view->special = true; null_view->special = true;
Assert(null_buffer->id == NullBufferID && null_view->id == NullViewID); 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 #if PLUGIN_RECORD_GC
GCInfoBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectDirectory, "gc")); GCInfoBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectFolder, "gc"));
GCInfoBuffer->special = true; GCInfoBuffer->special = true;
GCInfoBuffer->no_history = true; GCInfoBuffer->no_history = true;
#endif #endif
#if PLUGIN_RECORD_EVENTS #if PLUGIN_RECORD_EVENTS
EventBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectDirectory, "events")); EventBuffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectFolder, "events"));
EventBuffer->no_history = true; EventBuffer->no_history = true;
EventBuffer->special = true; EventBuffer->special = true;
#endif #endif
@@ -984,7 +1009,8 @@ int main(int argc, char **argv)
InitRender(); InitRender();
ReloadFont(PathToFont, (U32)FontSize); ReloadFont(PathToFont, (U32)FontSize);
CreateWind(); CreateWind();
InitOS(ReportWarningf); ReopenBuffer(GetBuffer(NullBufferID));
InitOS(ReportErrorf);
For (GlobalCommands) { For (GlobalCommands) {
if (it.binding.len != 0) { if (it.binding.len != 0) {
@@ -997,12 +1023,13 @@ int main(int argc, char **argv)
EnterOrEscapeKeySet = ParseKeyCached("escape | enter"); EnterOrEscapeKeySet = ParseKeyCached("escape | enter");
AltEnterKeySet = ParseKeyCached("alt-enter"); AltEnterKeySet = ParseKeyCached("alt-enter");
ShiftEnterKeySet = ParseKeyCached("shift-enter"); ShiftEnterKeySet = ParseKeyCached("shift-enter");
CheckKeybindingColission();
#if PLUGIN_CONFIG #if PLUGIN_CONFIG
{ {
Scratch scratch; Scratch scratch;
GlobalConfigBufferID = LoadConfig(Format(scratch, "%S/config.te", ConfigDir)); GlobalConfigBufferID = LoadConfig(Format(scratch, "%S/config.te", ConfigFolder));
} }
#endif #endif
for (int i = 1; i < argc; i += 1) { for (int i = 1; i < argc; i += 1) {
@@ -1052,7 +1079,5 @@ int main(int argc, char **argv)
SDL_DestroyWindow(SDLWindow); SDL_DestroyWindow(SDLWindow);
SDL_Quit(); SDL_Quit();
EndProfiler();
return 0; return 0;
} }

View File

@@ -6,7 +6,11 @@ struct ViewID { Int id; View *o; };
struct WindowID { Int id; Window *o; }; struct WindowID { Int id; Window *o; };
union Range { struct { Int min; Int max; }; Int e[2]; }; union Range { struct { Int min; Int max; }; Int e[2]; };
struct Caret { union { Range range; Int pos[2]; }; Int ifront;}; struct Caret { union { Range range; Int pos[2]; }; Int ifront;};
struct XY { Int col; Int line; }; union XY {
struct {Int col; Int line;};
struct {Int x; Int y; };
};
typedef void Function(); typedef void Function();
struct FunctionData { struct FunctionData {
@@ -41,11 +45,33 @@ enum UIAction {
UIAction_No, 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 { enum OpenKind {
OpenKind_Invalid, OpenKind_Invalid,
OpenKind_Skip, OpenKind_Skip,
OpenKind_Exec, OpenKind_Exec,
OpenKind_BackgroundExec,
OpenKind_Goto, OpenKind_Goto,
OpenKind_Command, OpenKind_Command,
#if PLUGIN_CONFIG #if PLUGIN_CONFIG
@@ -64,15 +90,18 @@ struct ResolvedOpen {
OpenKind kind; OpenKind kind;
String path; String path;
Int line, col; Int line, col;
bool existing_buffer;
struct {
U32 existing_buffer : 1;
U32 exec_in_background : 1;
U32 use_python_shell : 1;
};
}; };
struct Buffer { struct Buffer {
BufferID id; BufferID id;
String name; String name;
Int begin_frame_change_id;
Int change_id; Int change_id;
Int user_change_id;
Int file_mod_time; Int file_mod_time;
union { union {
@@ -142,7 +171,7 @@ struct Window {
double mouse_scroller_offset; double mouse_scroller_offset;
int z; int z;
double weight; double weight;
Caret search_bar_anchor; Caret search_bar_anchor; // maybe move to view @todo
GotoCrumb begin_frame_crumb; GotoCrumb begin_frame_crumb;
Array<GotoCrumb> goto_history; Array<GotoCrumb> goto_history;
@@ -291,10 +320,10 @@ struct Register_Command {
}; };
#define RegisterCommand(name, binding, docs) Register_Command RC__##name(&GlobalCommands, name, #name, binding, docs) #define RegisterCommand(name, binding, docs) Register_Command RC__##name(&GlobalCommands, name, #name, binding, docs)
#define RegisterCoroutineCommand(name, binding, docs, ...) void CMD_##name() {\ #define RegisterCoroutineCommand(name, binding, docs, ...) void CMD_##name() {\
CoRemove(#name);\ RemoveCoroutine(#name);\
CoData *data = CoAdd(name);\ CCtx *data = AddCoroutine(name);\
__VA_ARGS__\ __VA_ARGS__\
CoResume(data);\ ResumeCoroutine(data);\
}\ }\
Register_Command RC__##name(&GlobalCommands, CMD_##name, #name, binding, docs) Register_Command RC__##name(&GlobalCommands, CMD_##name, #name, binding, docs)
@@ -316,7 +345,6 @@ constexpr int DIR_UP = 3;
constexpr int DIR_COUNT = 4; constexpr int DIR_COUNT = 4;
constexpr bool CTRL_PRESSED = true; constexpr bool CTRL_PRESSED = true;
constexpr bool SHIFT_PRESS = true; constexpr bool SHIFT_PRESS = true;
constexpr bool KILL_SELECTION = true;
constexpr Int LAST_LINE = INT64_MAX; constexpr Int LAST_LINE = INT64_MAX;
BSet GetBSet(struct Window *window); BSet GetBSet(struct Window *window);
@@ -338,7 +366,6 @@ 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 JumpTempBuffer(BSet *set, String buffer_name = ""); void JumpTempBuffer(BSet *set, String buffer_name = "");
View *FindView(BufferID buffer_id, View *default_view = NULL);
bool operator==(BufferID a, BufferID 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==(ViewID a, ViewID b) { return a.id == b.id; }

View File

@@ -35,7 +35,7 @@ UIAction WaitForUIAction(mco_coro *co, BSet main) {
break; break;
} }
CoYield(co); Yield(co);
} }
Close(main.buffer->id); Close(main.buffer->id);
return result; return result;
@@ -84,7 +84,6 @@ void DetectUserFileCallback(Window *window, ResolvedOpen *resolved) {
String16 QueryUserString(mco_coro *co, String ask) { String16 QueryUserString(mco_coro *co, String ask) {
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
Buffer *original_buffer = main.buffer;
JumpTempBuffer(&main); JumpTempBuffer(&main);
NextActiveWindowID = main.window->id; NextActiveWindowID = main.window->id;
RawAppendf(main.buffer, R"==( RawAppendf(main.buffer, R"==(
@@ -113,11 +112,49 @@ String16 QueryUserString(mco_coro *co, String ask) {
return GetString(main.buffer, a.range); 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) { String QueryUserFile(mco_coro *co) {
#if PLUGIN_DIRECTORY_NAVIGATION #if PLUGIN_DIRECTORY_NAVIGATION
Window *window = GetWindow(PrimaryWindowID); Window *window = GetWindow(PrimaryWindowID);
ViewID original_view = window->active_view; ViewID original_view = window->active_view;
Open(GetPrimaryDirectory()); 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; window->after_resolve_open_hook = DetectUserFileCallback;
String filename = ""; String filename = "";
for (;;) { for (;;) {
@@ -130,7 +167,7 @@ String QueryUserFile(mco_coro *co) {
break; break;
} }
CoYield(co); Yield(co);
} }
window->active_view = original_view; window->active_view = original_view;
return window->ui_query_file; return window->ui_query_file;
@@ -175,12 +212,139 @@ void MouseLoadWord(Event event, ResolveOpenMeta meta = ResolveOpenMeta_Normal) {
} }
} }
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 = &it;
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 ResolveOpen(Allocator alo, Window *window, String path, ResolveOpenMeta meta) {
ResolvedOpen result = {}; ResolvedOpen result = {};
path = Trim(path); path = Trim(path);
bool exec = !(ResolveOpenMeta_DontExec & meta); bool exec = !(ResolveOpenMeta_DontExec & meta);
#if PLUGIN_CONFIG #if PLUGIN_CONFIG
path = InsertVariables(alo, path);
if (exec && result.kind == OpenKind_Invalid && StartsWith(path, ":Set ")) { if (exec && result.kind == OpenKind_Invalid && StartsWith(path, ":Set ")) {
result.kind = OpenKind_Set; result.kind = OpenKind_Set;
result.path = Skip(path, 5); result.path = Skip(path, 5);
@@ -202,7 +366,8 @@ ResolvedOpen ResolveOpen(Allocator alo, Window *window, String path, ResolveOpen
// !!exec_hidden // !!exec_hidden
if (exec && result.kind == OpenKind_Invalid && StartsWith(path, "!!")) { if (exec && result.kind == OpenKind_Invalid && StartsWith(path, "!!")) {
result.kind = OpenKind_BackgroundExec; result.kind = OpenKind_Exec;
result.exec_in_background = 1;
result.path = Skip(path, 2); result.path = Skip(path, 2);
} }
@@ -212,11 +377,18 @@ ResolvedOpen ResolveOpen(Allocator alo, Window *window, String path, ResolveOpen
result.path = Skip(path, 1); 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 // https://web
bool web = StartsWith(path, "https://") || StartsWith(path, "http://"); bool web = StartsWith(path, "https://") || StartsWith(path, "http://");
if (exec && result.kind == OpenKind_Invalid && web) { if (exec && result.kind == OpenKind_Invalid && web) {
result.path = Format(alo, "%S %S", InternetBrowser, path); result.path = Format(alo, "%S %S", InternetBrowser, path);
result.kind = OpenKind_BackgroundExec; result.kind = OpenKind_Exec;
result.exec_in_background = 1;
} }
// commit 3kc09as92 // commit 3kc09as92
@@ -248,18 +420,50 @@ ResolvedOpen ResolveOpen(Allocator alo, Window *window, String path, ResolveOpen
} }
path = {pstart.data, (Int)(p.data - pstart.data)}; path = {pstart.data, (Int)(p.data - pstart.data)};
if (At(p, 0) == ':') { // For (LINE:COLUMN): error: - we can either backtrack at the end since we are including
p = Skip(p, 1); // the parenthesis and whitespace or alternatively we can look for patterns on every
result.line = SkipInt(&p); // character move in the loop... For now let's do backtracking. This doesn't handle all paths
if (At(p, 0) == ':') { // but not sure if that's even what we want. ALL paths is hard.
p = Skip(p, 1); {
Int b = SkipInt(&p); Int i = path.len - 1;
result.col = b; if (At(path, i) == ')') {
i -= 1;
Int end = i;
while (IsDigit(At(path, i))) {
i -= 1;
} }
} else if (At(p, 0) == '(') { 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); p = Skip(p, 1);
result.line = SkipInt(&p); result.line = SkipInt(&p);
if (At(p, 0) == ',') { if (At(p, 0) == ':') {
p = Skip(p, 1); p = Skip(p, 1);
Int b = SkipInt(&p); Int b = SkipInt(&p);
result.col = b; result.col = b;
@@ -282,7 +486,9 @@ ResolvedOpen ResolveOpen(Allocator alo, Window *window, String path, ResolveOpen
String rel_path = Format(alo, "%S/%S", GetDirectory(window), path); String rel_path = Format(alo, "%S/%S", GetDirectory(window), path);
existing_buffer = GetBuffer(rel_path, NULL); existing_buffer = GetBuffer(rel_path, NULL);
if (existing_buffer || FileExists(rel_path)) { if (existing_buffer || FileExists(rel_path)) {
result.existing_buffer = existing_buffer; if (existing_buffer) {
result.existing_buffer = 1;
}
result.path = rel_path; result.path = rel_path;
result.kind = OpenKind_Goto; result.kind = OpenKind_Goto;
} }
@@ -324,14 +530,28 @@ BSet Open(Window *window, String path, ResolveOpenMeta meta, bool set_active = t
} }
CenterView(window->id); CenterView(window->id);
} else if (o.kind == OpenKind_Exec) { } 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) { if (set_active) {
NextActiveWindowID = set.window->id; NextActiveWindowID = set.window->id;
} }
JumpTempBuffer(&set); JumpTempBuffer(&set);
Exec(set.view->id, false, o.path, GetPrimaryDirectory()); RawAppend(set.buffer, u"\n");
} else if (o.kind == OpenKind_BackgroundExec) { args.output_view = set.view->id;
// this shouldn't change the focus/window/view }
Exec(NullViewID, false, o.path, GetPrimaryDirectory());
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) { } else if (o.kind == OpenKind_Command) {
EvalCommand(o.path); EvalCommand(o.path);
} }

View File

@@ -20,10 +20,60 @@ void Dealloc(View *view) {
Dealloc(view->carets.allocator, view); Dealloc(view->carets.allocator, view);
} }
// TODO: default_view should probably be Views[0] but could break stuff View *GetView(ViewID id, View *default_view = Views[0]) {
API View *FindView(ViewID view_id, View *default_view = NULL) { Int left = 0;
Int right = Views.len - 1;
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 result;
}
View *GetViewForBuffer(Buffer *buffer, bool *is_active = NULL) {
View *view = NULL;
if (is_active) {
*is_active = false;
}
BSet active = GetBSet(ActiveWindowID);
if (active.buffer->id == buffer->id) {
if (is_active) {
*is_active = true;
}
return active.view;
}
For(Views) { For(Views) {
if (it->id == view_id) { if (it->active_buffer != buffer->id) {
continue;
}
view = it;
break;
}
if (!view) {
view = CreateView(buffer->id);
}
return view;
}
API View *GetView(String name, View *default_view = Views[0]) {
For (Views) {
Buffer *buffer = GetBuffer(it->active_buffer);
if (buffer->name == name) {
return it; return it;
} }
} }
@@ -39,20 +89,6 @@ API View *FindView(BufferID buffer_id, View *default_view) {
return default_view; return default_view;
} }
API View *FindView(String name, View *default_view = NULL) {
For(Views) {
Buffer *buffer = GetBuffer(it->active_buffer);
if (buffer->name == name) {
return it;
}
}
return default_view;
}
API View *GetView(ViewID id) {
return FindView(id, Views[0]);
}
API View *OpenBufferView(String name) { API View *OpenBufferView(String name) {
Buffer *buffer = BufferOpenFile(name); Buffer *buffer = BufferOpenFile(name);
View *view = CreateView(buffer->id); View *view = CreateView(buffer->id);
@@ -76,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;
@@ -110,45 +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;
} }
char16_t GetIndentChar() { Array<Range> GetSelectedLinesSortedExclusive(Allocator allocator, View *view) {
char16_t c = u' '; Scratch scratch(allocator);
if (IndentKindWhichIsTabsOrSpaces == "spaces") { Buffer *buffer = GetBuffer(view->active_buffer);
c = u' '; Array<Caret> caret_copy = TightCopy(scratch, view->carets);
} else if (IndentKindWhichIsTabsOrSpaces == "tabs") { Array<Caret> temp = TightCopy(scratch, view->carets);
c = u'\t'; 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 { } else {
ReportErrorf("Invalid IndentKindWhichIsTabsOrSpaces value: %S", IndentKindWhichIsTabsOrSpaces); Add(&result, line_range);
} }
return c;
} }
return result;
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) {
@@ -159,12 +201,11 @@ 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);
} }
// WARNING: Don't use in user facing stuff // WARNING: Don't use in user facing stuff
@@ -378,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;
// @todo: this doesn't work well at the end of buffer
struct XYPair {
XY front;
XY back;
};
Buffer *buffer = GetBuffer(view->active_buffer); Buffer *buffer = GetBuffer(view->active_buffer);
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
MergeCarets(buffer, &view->carets);
// Save caret positions to fix them at end to the expected incremented by one positions
// :PreserveXYCarets (kind of)
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];
@@ -431,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) {
@@ -493,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) {
@@ -504,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);
@@ -539,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 (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(&not_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 (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;
} }
@@ -596,7 +681,7 @@ 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;
} }
@@ -625,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;

View File

@@ -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);
Assert(it_buffer); // Probably we are missing something when GCing / closing
if (it_buffer && it_buffer->name == buffer_name) {
return it; 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;
@@ -192,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;
} }
@@ -203,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];
@@ -274,38 +279,35 @@ void GotoNextInList(Window *window, Int line_offset = 1) {
bool opened = false; bool opened = false;
for (Int i = line + line_offset; i >= 0 && i < buffer_goto->line_starts.len; i += line_offset) { for (Int i = line + line_offset; i >= 0 && i < buffer_goto->line_starts.len; i += line_offset) {
Range line_range = GetLineRangeWithoutNL(buffer_goto, i); Range line_range = GetLineRangeWithoutNL(buffer_goto, i);
String16 line = GetString(buffer_goto, line_range); String16 string_line = GetString(buffer_goto, line_range);
{ {
Int idx = 0; Int idx = 0;
String16 delim = u"||>"; String16 delim = u"||>";
if (Seek(line, delim, &idx, SeekFlag_None)) { if (Seek(string_line, delim, &idx, SeekFlag_None)) {
line = Skip(line, idx + delim.len); string_line = Skip(string_line, idx + delim.len);
} }
} }
view_goto->carets[0] = MakeCaret(line_range.min); view_goto->carets[0] = MakeCaret(line_range.min);
window->goto_list_pos = line_range.min; window->goto_list_pos = line_range.min;
line = Trim(line); string_line = Trim(string_line);
MergeCarets(buffer_goto, &view_goto->carets); MergeCarets(buffer_goto, &view_goto->carets);
IF_DEBUG(AssertRanges(view_goto->carets)); IF_DEBUG(AssertRanges(view_goto->carets));
if (line.len == 0) { if (string_line.len == 0) {
continue; continue;
} }
Buffer *active_view_buffer = GetBuffer(active_view->active_buffer); Range before_jump_range = active_view->carets[0].range;
Int p = active_view->carets[0].range.min;
Int active_view_line = PosToLine(active_view_buffer, p);
BSet set = Open(line, ResolveOpenMeta_DontError | ResolveOpenMeta_DontExec); BSet set = Open(string_line, ResolveOpenMeta_DontError | ResolveOpenMeta_DontExec);
if (set.window == NULL) { if (set.window == NULL) {
continue; continue;
} }
if (set.view == active_view) { if (set.view == active_view) {
Int new_line = PosToLine(set.buffer, set.view->carets[0].range.min); if (AreOverlapping(set.view->carets[0].range, before_jump_range)) {
if (active_view_line == new_line) {
continue; continue;
} }
} }
@@ -378,6 +380,14 @@ void CenterView(WindowID window) {
} }
} }
void CenterPrimaryViews() {
For (Windows) {
if (it->primary) {
CenterView(it->id);
}
}
}
BSet GetBSet(Window *window) { BSet GetBSet(Window *window) {
BSet set = {window}; BSet set = {window};
set.view = GetView(set.window->active_view); set.view = GetView(set.window->active_view);
@@ -391,39 +401,76 @@ BSet GetBSet(WindowID window_id) {
return result; return result;
} }
String GetDirectory(Window *window) {
BSet set = GetBSet(window->id);
return GetDirectory(set.buffer);
}
String GetPrimaryDirectory() { String GetPrimaryDirectory() {
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
return GetDirectory(main.buffer); return GetDirectory(main.buffer);
} }
String GetDirectory(Window *window) { struct IsOnScreenResult {
BSet main = GetBSet(PrimaryWindowID); bool caret_on_screen;
return GetDirectory(main.buffer); 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) { void MoveCursorByPageSize(Window *window, int direction, bool shift = false) {
Assert(direction == DIR_UP || direction == DIR_DOWN); Assert(direction == DIR_UP || direction == DIR_DOWN);
BSet set = GetBSet(window); BSet set = GetBSet(window);
IsOnScreenResult is_on_screen_res = IsMainCaretOnScreen(window);
Rect2I visible_cells_rect = GetVisibleCells(window); Rect2I visible_cells_rect = GetVisibleCells(window);
Int y = GetSize(visible_cells_rect).y - 2; Int y = GetSize(visible_cells_rect).y - 2;
if (direction == DIR_UP) y = -y; if (direction == DIR_UP) {
y = -y;
}
For (set.view->carets) { For (set.view->carets) {
XY xy = PosToXY(set.buffer, GetFront(it)); XY xy = PosToXY(set.buffer, GetFront(it));
xy.col = 0;
if (direction == DIR_DOWN && xy.line == set.buffer->line_starts.len - 1) { if (direction == DIR_DOWN && xy.line == set.buffer->line_starts.len - 1) {
Range line_range = GetLineRange(set.buffer, xy.line); Range line_range = GetLineRange(set.buffer, xy.line);
xy.col = line_range.max - line_range.min; xy.col = line_range.max - line_range.min;
} else if (direction == DIR_UP && xy.line == 0) {
xy.col = 0;
} }
xy.line += y; xy.line += y;
Int pos = XYToPos(set.buffer, xy); Int pos = XYToPosWithoutNL(set.buffer, xy);
if (shift) { if (shift) {
it = SetFront(it, pos); it = SetFront(it, pos);
} else { } else {
it = MakeCaret(pos); it = MakeCaret(pos);
} }
} }
IsOnScreenResult r = IsMainCaretOnScreen(window);
if (!r.caret_on_screen) {
SetStoredOffsetFromTop(window, is_on_screen_res);
}
} }