Compare commits
8 Commits
4ad9e6bf20
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aff3403404 | ||
|
|
b04b566681 | ||
|
|
aa85d9420a | ||
|
|
4c21026842 | ||
|
|
cab882de60 | ||
|
|
fadf4cd698 | ||
|
|
94ee03800d | ||
|
|
207fc65fec |
121
README.md
121
README.md
@@ -1,9 +1,118 @@
|
||||
# te, a text editor
|
||||
# te
|
||||
|
||||

|
||||
A from-scratch text editor that aims to stay fast, hackable, and practical for day-to-day coding.
|
||||
|
||||
In order to build on windows you need to call build.bat from
|
||||
the x64 Native Command Tools prompt that ships with Visual Studio.
|
||||

|
||||
|
||||
For Linux you will need clang, SDL3 and libbacktrace libraries to be installed and
|
||||
available on your system. Just call build.sh when these are available.
|
||||
## What this project is
|
||||
|
||||
`te` is a native desktop editor written as a single C++ executable (SDL3 + OpenGL + custom editor core).
|
||||
It is built around a straightforward single-threaded architecture, with coroutines used for asynchronous workflows (searching, UI flows, process jobs) so behavior stays easy to reason about and debug.
|
||||
|
||||
The codebase includes its own:
|
||||
|
||||
- text buffer + undo/redo engine
|
||||
- window/view layout system
|
||||
- command system + keybinding parser
|
||||
- fuzzy selection UI
|
||||
- process execution and output streaming
|
||||
- built-in font fallback (baked into the binary)
|
||||
|
||||
## Highlights
|
||||
|
||||
- VS Code-style keybindings and command palette (`Ctrl+Shift+P`)
|
||||
- Multi-cursor editing (keyboard and mouse workflows)
|
||||
- Fuzzy-open patterns for commands, files, and open buffers
|
||||
- Project-wide search (`Ctrl+Shift+F`) and replace-all workflow
|
||||
- Shell integration directly from editor input:
|
||||
- `:Command` to run editor commands
|
||||
- `:Set ...` to configure options and keybindings
|
||||
- `!cmd` / `!!cmd` to execute shell commands
|
||||
- `py:...` to execute Python snippets via discovered Python interpreter
|
||||
- Build panel integration (`Ctrl+B`, `Alt+B`, etc.) with configurable build commands
|
||||
- Config-driven behavior (font, colors, project commands, keymaps, paths)
|
||||
|
||||
## Build
|
||||
|
||||
### Linux
|
||||
|
||||
Requirements:
|
||||
|
||||
- `clang`
|
||||
- `cmake`
|
||||
- `libbacktrace`
|
||||
- SDL3 (the build script can clone and install SDL when missing)
|
||||
|
||||
Build commands:
|
||||
|
||||
```bash
|
||||
./build.sh # debug build (default)
|
||||
./build.sh release # optimized build
|
||||
./build.sh slow # extra asserts/instrumentation path
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
./build/te
|
||||
./build/te path/to/file.cpp
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
Use the **x64 Native Tools Command Prompt for Visual Studio**.
|
||||
|
||||
Requirements:
|
||||
|
||||
- Visual Studio C++ toolchain (`cl`, `msbuild`)
|
||||
- `cmake`
|
||||
|
||||
Build commands:
|
||||
|
||||
```bat
|
||||
build.bat
|
||||
build.bat release
|
||||
build.bat slow
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bat
|
||||
build\te.exe
|
||||
build\te.exe path\to\file.cpp
|
||||
```
|
||||
|
||||
## First steps in the editor
|
||||
|
||||
- `Ctrl+N`: new buffer
|
||||
- `Ctrl+O`: open current file's folder / directory navigation view
|
||||
- `Ctrl+P`: fuzzy list of open buffers
|
||||
- `Ctrl+Shift+P`: all commands
|
||||
- `Ctrl+Q` or `F12`: open thing under caret (path / link / command)
|
||||
- `Ctrl+F`, `F3`, `Shift+F3`: in-buffer search flow
|
||||
- `Ctrl+Shift+F`: interactive search across open project buffers
|
||||
- `Alt+Shift+Up/Down`: add cursors vertically
|
||||
- `Ctrl+B`: run Build1 command (configurable)
|
||||
|
||||
## Configuration
|
||||
|
||||
On startup, `te` loads a config file from SDL's app preference directory (`config.te`).
|
||||
You can also pass additional `.te` files as CLI arguments.
|
||||
|
||||
Useful examples:
|
||||
|
||||
```text
|
||||
:Set FontSize 16
|
||||
:Set PathToFont '/path/to/font.ttf'
|
||||
:Set Build1OnUnix 'sh build.sh release'
|
||||
:Set Build1OnWindows 'build.bat release'
|
||||
:Set TextColor ff202020
|
||||
:Set BackgroundColor fffdf6e3
|
||||
```
|
||||
|
||||
## Notes on architecture
|
||||
|
||||
- Single-threaded main loop (event/update/render)
|
||||
- Coroutines for async editor tasks instead of multi-threaded complexity
|
||||
- "Plugin" modules are integrated in the source tree and compiled in
|
||||
- Focus on predictable behavior and low-latency interaction
|
||||
|
||||
20
build.sh
20
build.sh
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
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
|
||||
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
|
||||
if [ -v addr ]; then echo "[address sanitizer build]"; fi
|
||||
|
||||
mkdir -p build
|
||||
|
||||
@@ -13,7 +14,8 @@ if [ ! -e "src/external/SDL" ]; then
|
||||
git clone https://github.com/libsdl-org/SDL.git
|
||||
cd SDL
|
||||
git checkout release-3.2.30
|
||||
# cmake -S . -B build_linux -DCMAKE_BUILD_TYPE=Release -DSDL_PIPEWIRE=OFF
|
||||
# We need older version of SDL3 because there is a bug on wayland that
|
||||
# doubles click events and it's kind of unusable
|
||||
cmake -S . -B build_linux -DSDL_PIPEWIRE=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr
|
||||
cd build_linux
|
||||
sudo make -j16 install
|
||||
@@ -27,8 +29,10 @@ flags="-Wall -Wextra -Werror -Wformat=2 -Wundef -Wshadow -Wno-missing-field-init
|
||||
-g -fdiagnostics-absolute-paths \
|
||||
-nostdlib++ -fno-exceptions"
|
||||
|
||||
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
|
||||
if [ -v debug ]; then flags="$flags -fsanitize=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
|
||||
if [ -v addr ]; then flags="$flags -fsanitize=address"; fi
|
||||
|
||||
time clang -o te $flags ../src/text_editor.cpp $I -lSDL3 -lm -lbacktrace
|
||||
./te :RunTests
|
||||
@@ -256,4 +256,4 @@ void CMD_SelectComment() {
|
||||
}
|
||||
MergeCarets(active.buffer, &active.view->carets);
|
||||
|
||||
} RegisterCommand(CMD_SelectComment, "ctrl-shift-l", "Find /* and */ and select the content in between");
|
||||
} RegisterCommand(CMD_SelectComment, "ctrl-semicolon", "Find /* and */ and select the content in between");
|
||||
@@ -80,7 +80,8 @@ void UpdateCoroutines(Event *event) {
|
||||
#endif
|
||||
|
||||
double took = GetTimeSeconds() - start;
|
||||
if (took > (0.016666 / 3.0)) {
|
||||
bool dont_loop_on_coroutines_when_budget_is_ok_exit_immediately = Testing;
|
||||
if (dont_loop_on_coroutines_when_budget_is_ok_exit_immediately || (took > (0.016666 / 3.0))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
SDL_Window *SDLWindow;
|
||||
SDL_GLContext SDL_WindowGLContext;
|
||||
SDL_Cursor *SDL_MouseCursor;
|
||||
SDL_SystemCursor SDL_MouseCursorLastID;
|
||||
bool IsInFullscreen;
|
||||
int FullScreenSizeX, FullScreenSizeY;
|
||||
int FullScreenPositionX, FullScreenPositionY;
|
||||
@@ -10,6 +13,7 @@ bool SearchWordBoundary = false;
|
||||
bool BreakOnError = false;
|
||||
Int ErrorCount;
|
||||
bool DebugTraceBufferInits = false;
|
||||
bool Testing = false;
|
||||
|
||||
Allocator SysAllocator = {SystemAllocatorProc};
|
||||
float DPIScale = 1.0f;
|
||||
@@ -61,6 +65,8 @@ Vec2I MouseMiddleAnchor;
|
||||
|
||||
RandomSeed UniqueBufferNameSeed = {};
|
||||
Array<Event> EventPlayback;
|
||||
Array<Event> MacroPlayback;
|
||||
bool RecordingMacro = false;
|
||||
BlockArena Perm;
|
||||
|
||||
// clipboard
|
||||
|
||||
@@ -37,7 +37,7 @@ void CMD_DedentSelectedLines() {
|
||||
void CMD_DuplicateLineDown() {
|
||||
BSet active = GetBSet(ActiveWindowID);
|
||||
DuplicateLine(active.view, DIR_DOWN);
|
||||
} RegisterCommand(CMD_DuplicateLineDown, "ctrl-alt-down", "");
|
||||
} RegisterCommand(CMD_DuplicateLineDown, "ctrl-shift-alt-down", "");
|
||||
|
||||
void CMD_CreateCursorDown() {
|
||||
BSet active = GetBSet(ActiveWindowID);
|
||||
@@ -72,7 +72,7 @@ void CMD_MoveDown() {
|
||||
void CMD_DuplicateLineUp() {
|
||||
BSet active = GetBSet(ActiveWindowID);
|
||||
DuplicateLine(active.view, DIR_UP);
|
||||
} RegisterCommand(CMD_DuplicateLineUp, "ctrl-alt-up", "");
|
||||
} RegisterCommand(CMD_DuplicateLineUp, "ctrl-shift-alt-up", "");
|
||||
|
||||
void CMD_CreateCursorUp() {
|
||||
BSet active = GetBSet(ActiveWindowID);
|
||||
@@ -234,6 +234,12 @@ void CMD_NewLine() {
|
||||
IndentedNewLine(active.view);
|
||||
} RegisterCommand(CMD_NewLine, "enter | shift-enter", "");
|
||||
|
||||
void CMD_SearchAllOccurences() {
|
||||
BSet active = GetBSet(ActiveWindowID);
|
||||
String16 needle = GetString(active.buffer, active.view->carets[0].range);
|
||||
SelectAllOccurences(active.view, needle);
|
||||
} RegisterCommand(CMD_SearchAllOccurences, "ctrl-shift-l", "Use the selected word as needle and selects all the possible occurences in current buffer");
|
||||
|
||||
void CMD_CreateCaretOnNextFind() {
|
||||
BSet active = GetBSet(ActiveWindowID);
|
||||
String16 string = GetString(active.buffer, active.view->carets[0].range);
|
||||
@@ -247,3 +253,21 @@ void CMD_ClearCarets() {
|
||||
active.view->carets.len = 1;
|
||||
active.view->carets[0] = MakeCaret(GetFront(active.view->carets[0]));
|
||||
} RegisterCommand(CMD_ClearCarets, "escape", "Clear all carets and reset to 1 caret, also do some windowing stuff that closes things on escape");
|
||||
|
||||
void CMD_ToggleMacroRecording() {
|
||||
if (RecordingMacro) {
|
||||
RecordingMacro = false;
|
||||
Pop(&MacroPlayback); // Remove the ctrl-m from macro playback thing
|
||||
} else {
|
||||
RecordingMacro = true;
|
||||
MacroPlayback.len = 0;
|
||||
}
|
||||
} RegisterCommand(CMD_ToggleMacroRecording, "ctrl-m", "Start recording a macro");
|
||||
|
||||
void CMD_PlayMacro() {
|
||||
if (RecordingMacro) {
|
||||
return;
|
||||
}
|
||||
|
||||
For (MacroPlayback) Add(&EventPlayback, it);
|
||||
} RegisterCommand(CMD_PlayMacro, "alt-m", "Start playing back a macro recording");
|
||||
@@ -88,17 +88,17 @@ void Set(String string) {
|
||||
string = Skip(string, 1);
|
||||
String quote = SkipUntil(&string, {&c, 1});
|
||||
ExpectP(At(string, 0) == c, ":Set %S <error here>, unclosed quote", name);
|
||||
ReportConsolef(":Set %S %c%S%c", name, c, quote, c);
|
||||
// ReportConsolef(":Set %S %c%S%c", name, c, quote, c);
|
||||
*var->string = Intern(&GlobalInternTable, quote);
|
||||
} else if (var->type == VariableType_Int) {
|
||||
ExpectP(IsDigit(At(string, 0)), "Expected an integer to follow the command name, instead got: %S", string);
|
||||
Int number = SkipInt(&string);
|
||||
ReportConsolef(":Set %S %lld", name, number);
|
||||
// ReportConsolef(":Set %S %lld", name, number);
|
||||
*var->i = number;
|
||||
} else if (var->type == VariableType_Float) {
|
||||
ExpectP(IsDigit(At(string, 0)), "Expected float to follow the command name, instead got: %S", string);
|
||||
Float number = SkipFloat(&string);
|
||||
ReportConsolef(":Set %S %f", name, number);
|
||||
// ReportConsolef(":Set %S %f", name, number);
|
||||
*var->f = number;
|
||||
} else if (var->type == VariableType_Color) {
|
||||
ExpectP(IsHexDigit(At(string, 0)), "Expected hex integer to follow the command name, instead got: %S", string);
|
||||
@@ -107,7 +107,7 @@ void Set(String string) {
|
||||
string = Skip(string, 1);
|
||||
begin.len += 1;
|
||||
}
|
||||
ReportConsolef(":Set %S %S", name, begin);
|
||||
// ReportConsolef(":Set %S %S", name, begin);
|
||||
var->color->value = (uint32_t)strtoll(begin.data, NULL, 16);
|
||||
} ElseInvalidCodepath();
|
||||
|
||||
|
||||
@@ -8,6 +8,16 @@ void CMD_OpenUpFolder() {
|
||||
Open(name);
|
||||
} RegisterCommand(CMD_OpenUpFolder, "ctrl-o", "Open current's file directory or up directory in other cases");
|
||||
|
||||
void CMD_OpenSystemFileBrowser() {
|
||||
Scratch scratch;
|
||||
String string = GetPrimaryDirectory();
|
||||
#if OS_WINDOWS
|
||||
Open(Format(scratch, "!!explorer %S", string));
|
||||
#else
|
||||
Open(Format(scratch, "!!xdg-open %S & disown", string));
|
||||
#endif
|
||||
} RegisterCommand(CMD_OpenSystemFileBrowser, "ctrl-alt-r", "Opens the directory of current file in the systems file browser");
|
||||
|
||||
void InsertDirectoryNavigation(Buffer *buffer) {
|
||||
Assert(buffer->is_dir);
|
||||
Scratch scratch;
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
bool Testing = true;
|
||||
#if PLUGIN_TESTS
|
||||
|
||||
|
||||
void Wait(mco_coro *co) {
|
||||
Add(&EventPlayback, {EVENT_KIND_INVALID});
|
||||
for (Event *event = Yield(co); event->kind != EVENT_KIND_INVALID; event = Yield(co)) {
|
||||
{Event ev = {};ev.kind = EVENT_KIND_INVALID; Add(&EventPlayback, ev);}
|
||||
Event *event = NULL;
|
||||
for (event = Yield(co); event->kind != EVENT_KIND_INVALID; event = Yield(co)) {
|
||||
}
|
||||
}
|
||||
|
||||
void CO_FirstTest(mco_coro *co) {
|
||||
void Wait(mco_coro *co, int updates) {
|
||||
for (int i = 0; i < updates; i += 1) {
|
||||
{Event ev = {};ev.kind = EVENT_UPDATE; Add(&EventPlayback, ev);}
|
||||
}
|
||||
Wait(co);
|
||||
}
|
||||
|
||||
void OpenCloseCodeTest(mco_coro *co) {
|
||||
Int initial_buffers_count = Buffers.len;
|
||||
CO_OpenCode(co);
|
||||
Assert(Buffers.len > initial_buffers_count);
|
||||
CO_CloseAll(co);
|
||||
Wait(co);
|
||||
Assert(initial_buffers_count - 1 == Buffers.len);
|
||||
}
|
||||
|
||||
void CO_RunTests(mco_coro *co) {
|
||||
Testing = true;
|
||||
WaitForEvents = false;
|
||||
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_RETURN; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_RETURN; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_RETURN; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
@@ -37,4 +56,68 @@ void CO_FirstTest(mco_coro *co) {
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "f"; Add(&EventPlayback, ev);}
|
||||
Wait(co);
|
||||
|
||||
} RegisterCoroutineCommand(CO_FirstTest, "", "Basic tests");
|
||||
String16 result = uR"FOO(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Memes and stuff)FOO";
|
||||
BSet set = GetBSet(PrimaryWindowID);
|
||||
Assert(AreEqual(result, GetString(set.buffer)));
|
||||
|
||||
// Test the box selection
|
||||
CMD_New();
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_RETURN; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_RETURN; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_RETURN; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_RETURN; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_RETURN; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_RETURN; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_RETURN; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_LSHIFT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.shift = 1; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.shift = 1; ev.text = "M"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "e"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "m"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "e"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "s"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_SPACE; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = " "; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_LSHIFT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.shift = 1; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "a"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "n"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "d"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_SPACE; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = " "; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "s"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "t"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "u"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "f"; Add(&EventPlayback, ev);}
|
||||
{Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1412; ev.ywindow = 1032; ev.xmouse = 1234; ev.ymouse = 594; ev.text = "f"; Add(&EventPlayback, ev);}
|
||||
Wait(co);
|
||||
|
||||
result = uR"FOO(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Memes and stuff)FOO";
|
||||
set = GetBSet(PrimaryWindowID);
|
||||
Assert(AreEqual(result, GetString(set.buffer)));
|
||||
|
||||
if (ErrorCount != 0) {
|
||||
Scratch scratch;
|
||||
String string = AllocCharString(scratch, LogBuffer);
|
||||
printf("TEXT EDITOR ERRORS!\n==========================\n%.*s", (int)string.len, string.data);
|
||||
}
|
||||
printf("RunTests OK\n");
|
||||
fflush(stdout);
|
||||
void CMD_QuitWithoutSaving();
|
||||
CMD_QuitWithoutSaving();
|
||||
} RegisterCoroutineCommand(CO_RunTests, "", "Basic tests");
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,14 +9,14 @@ void CMD_Prev() {
|
||||
main.window->skip_checkpoint = true;
|
||||
JumpBack(main.window);
|
||||
NextActiveWindowID = main.window->id;
|
||||
} RegisterCommand(CMD_Prev, "alt-q | mousex1", "Go to previous position (either previous view that was open or caret position) in the primary window");
|
||||
} RegisterCommand(CMD_Prev, "alt-q | mousex1 | ctrl-alt-minus", "Go to previous position (either previous view that was open or caret position) in the primary window");
|
||||
|
||||
void CMD_Next() {
|
||||
BSet main = GetBSet(PrimaryWindowID);
|
||||
main.window->skip_checkpoint = true;
|
||||
JumpForward(main.window);
|
||||
NextActiveWindowID = main.window->id;
|
||||
} RegisterCommand(CMD_Next, "alt-shift-q | mousex2", "Go to next position, after backtracking, in the primary window");
|
||||
} RegisterCommand(CMD_Next, "alt-shift-q | mousex2 | ctrl-shift-minus", "Go to next position, after backtracking, in the primary window");
|
||||
|
||||
void CMD_FocusLeftWindow() {
|
||||
NextActiveWindowID = SwitchWindow(DIR_LEFT)->id;
|
||||
@@ -74,9 +74,9 @@ void CMD_CloseWindow() {
|
||||
void CMD_GotoNextInList() {
|
||||
BSet main = GetBSet(PrimaryWindowID);
|
||||
GotoNextInList(main.window, 1);
|
||||
} RegisterCommand(CMD_GotoNextInList, "ctrl-e", "For example: when jumping from build panel to build error, a jump point is setup, user can click this button to go over to the next compiler error");
|
||||
} RegisterCommand(CMD_GotoNextInList, "ctrl-e | f8", "For example: when jumping from build panel to build error, a jump point is setup, user can click this button to go over to the next compiler error");
|
||||
|
||||
void CMD_GotoPrevInList() {
|
||||
BSet main = GetBSet(PrimaryWindowID);
|
||||
GotoNextInList(main.window, -1);
|
||||
} RegisterCommand(CMD_GotoPrevInList, "alt-e", "For example: when jumping from build panel to build error, a jump point is setup, user can click this button to go over to the previous compiler error");
|
||||
} RegisterCommand(CMD_GotoPrevInList, "alt-e | shift-f8", "For example: when jumping from build panel to build error, a jump point is setup, user can click this button to go over to the previous compiler error");
|
||||
|
||||
@@ -119,7 +119,7 @@ Font CreateFont(Atlas *atlas, int32_t size, String path) {
|
||||
}
|
||||
|
||||
stbtt_fontinfo stb_font;
|
||||
int success = stbtt_InitFont(&stb_font, (const unsigned char *)file.data, 0);
|
||||
int success = stbtt_InitFont(&stb_font, (const unsigned char *)file.data, 0);
|
||||
if (!success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -18,22 +18,20 @@ struct VertexList2D {
|
||||
|
||||
struct Shader {
|
||||
GLuint program; // linked program (vertex+fragment)
|
||||
GLint uni_invHalf; // uniform location for inv half-screen size
|
||||
GLint uni_texture; // sampler location
|
||||
GLint uni_invHalf; // uniform location for inv half-screen size
|
||||
GLint uni_texture; // sampler location
|
||||
};
|
||||
|
||||
VertexList2D Vertices;
|
||||
int64_t TotalVertexCount;
|
||||
int64_t TotalVertexCount;
|
||||
|
||||
unsigned VBO, VAO;
|
||||
Shader Shader2D;
|
||||
Shader Shader2D;
|
||||
BlockArena RenderArena;
|
||||
Rect2 CurrentScissor;
|
||||
Rect2 CurrentScissor;
|
||||
|
||||
Font PrimaryFont;
|
||||
Font SecondaryFont;
|
||||
|
||||
// ---------- shaders (ES3 / WebGL2) ----------
|
||||
static const char *glsl_vshader_es3 = R"==(#version 300 es
|
||||
precision highp float;
|
||||
|
||||
@@ -82,7 +80,6 @@ void GLDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLs
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- helper: compile/link ----------
|
||||
static GLuint CompileShaderSrc(GLenum kind, const char *src) {
|
||||
GLuint s = glCreateShader(kind);
|
||||
glShaderSource(s, 1, &src, NULL);
|
||||
@@ -137,7 +134,6 @@ Shader CreateShaderES3(const char *vsrc, const char *fsrc) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---------- InitRender for ES3 ----------
|
||||
void InitRender() {
|
||||
#if !OS_WASM
|
||||
glDebugMessageCallback(&GLDebugCallback, NULL);
|
||||
@@ -185,7 +181,6 @@ void BeginFrameRender(float wx, float wy) {
|
||||
CurrentScissor = Rect0Size(wx, wy);
|
||||
}
|
||||
|
||||
// ---------- EndFrameRender for ES3 ----------
|
||||
void EndFrameRender(float wx, float wy, Color color) {
|
||||
ProfileFunction();
|
||||
glEnable(GL_BLEND);
|
||||
@@ -391,6 +386,9 @@ void ReloadFont(String path, U32 size) {
|
||||
Scratch scratch;
|
||||
Atlas atlas = CreateAtlas(scratch, {2048, 2048});
|
||||
PrimaryFont = CreateFont(&atlas, (uint32_t)ClampBottom(2u, (U32)size), path);
|
||||
SecondaryFont = CreateFont(&atlas, 12, path);
|
||||
SecondaryFont.texture_id = PrimaryFont.texture_id = UploadAtlas(&atlas);
|
||||
PrimaryFont.texture_id = UploadAtlas(&atlas);
|
||||
}
|
||||
|
||||
void CleanupRender() {
|
||||
Dealloc(&PrimaryFont.glyphs);
|
||||
}
|
||||
@@ -1,11 +1,22 @@
|
||||
/*
|
||||
- [x] list_functions.sh and rg in general in the color mode is prinitng differently from terminal as well as output is truncated!
|
||||
- [ ] Syntax for executing commands from root of project
|
||||
- [ ] Syntax for executing commands from root of project, or maybe commands should be executed from root of the CurrentDirectory and there should be a cd command instead of OpenProject
|
||||
- [ ] Fuzzy search over executed command ouput
|
||||
- [ ] When inserting parenthesis and selection is there, put the parens on both sides?
|
||||
- [ ] KillProcess in console !!! - should also kill all the children ...........
|
||||
- [x] ctrl-e with these short main.c:290: breaks a little, need to first click ctrl-e and then alt-e to jump
|
||||
- [ ] Use command window without special fuzzy search features to type commands and stuff for executing shell etc..
|
||||
- [ ] I noticed WordComplete getting busted and returning cutdown words but not sure how to repro ...
|
||||
|
||||
## Monaco like design for familiarity
|
||||
- [x] Ctrl+Alt+- and Ctrl+Shift+- Jump back, jump forward
|
||||
- [x] ctrl-shift-l select all occurences of a string
|
||||
- [x] ctrl-alt-r open containing folder (file explorer system)
|
||||
- [x] ctrl-alt-MouseMove should do a box select with multiple cursors thing
|
||||
- [ ] ctrl-tab - switch file lister with instant hold release semantics?
|
||||
- [ ] SearchAndReplace how to do better?
|
||||
- [ ] ctrl-t find workspace symbols? how can we do it?
|
||||
- [ ] Snippet design?
|
||||
|
||||
- [ ] Make a platform layer and separate SDL stuff out
|
||||
- [x] ReplaceAll - heap-use-after-free address, how to debug? I think would be nice to iterate all buffer ids and their addresses along with the state
|
||||
@@ -13,6 +24,10 @@
|
||||
- [x] BRO, the caret teleports on linux when I press the arrow for too long
|
||||
- [ ] Report SDL newest vs SDL previous version on wayland
|
||||
|
||||
- [ ] Ctrl+Shift+ArrowDown at the end of buffer, doesn't capture characters on last line without new line
|
||||
- [ ] Remove -lbacktrace and add my backtrace library thing
|
||||
- [ ] Refactor build.sh to accept commands and remove build_web.sh
|
||||
|
||||
- [ ] Cleanups
|
||||
- [ ] How to enable framerate to be unlimited and not break scrolling?
|
||||
- [x] When dragging a file into the editor, would be nice if the file opened in the window user dropped the file into. Not the active window.
|
||||
@@ -33,7 +48,7 @@
|
||||
- [ ] Undo kinds (to enable history in fuzzy buffers)
|
||||
- [ ] Add undo kind. Snapshot kind, so that history is possible in weird buffers without paying a huge memory cost. The idea is that we would store the exact buffer state to replace with, editor would just save history of first line etc.
|
||||
|
||||
- [ ] Macros
|
||||
- [x] Macros
|
||||
- [ ] Regex
|
||||
- [ ] ctags based indexing
|
||||
- [ ] WordComplete
|
||||
@@ -91,6 +106,7 @@
|
||||
#define PLUGIN_REMEDYBG OS_WINDOWS
|
||||
#define PLUGIN_FILE_COMMANDS 1
|
||||
#define PLUGIN_WORD_COMPLETE 1
|
||||
#define PLUGIN_TESTS 1
|
||||
|
||||
#include "plugin_directory_navigation.h"
|
||||
#include "plugin_search_window.h"
|
||||
@@ -154,16 +170,13 @@ void SetMouseCursor(SDL_SystemCursor id) {
|
||||
}
|
||||
#else
|
||||
void SetMouseCursor(SDL_SystemCursor id) {
|
||||
static SDL_Cursor *SDL_MouseCursor;
|
||||
static SDL_SystemCursor last_id;
|
||||
|
||||
if (SDL_MouseCursor == NULL || last_id != id) {
|
||||
if (SDL_MouseCursor == NULL || SDL_MouseCursorLastID != id) {
|
||||
if (SDL_MouseCursor != NULL) {
|
||||
SDL_DestroyCursor(SDL_MouseCursor);
|
||||
}
|
||||
SDL_MouseCursor = SDL_CreateSystemCursor(id);
|
||||
SDL_SetCursor(SDL_MouseCursor);
|
||||
last_id = id;
|
||||
SDL_MouseCursorLastID = id;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -216,9 +229,6 @@ void SetMouseCursor(Event event) {
|
||||
}
|
||||
|
||||
void CMD_QuitWithoutSaving() {
|
||||
#if PLUGIN_REMEDYBG
|
||||
QuitDebugger();
|
||||
#endif
|
||||
AppIsRunning = false;
|
||||
} RegisterCommand(CMD_QuitWithoutSaving, "", "Self explanatory");
|
||||
|
||||
@@ -305,9 +315,30 @@ void OnCommand(Event event) {
|
||||
}
|
||||
}
|
||||
|
||||
Int p = ScreenSpaceToBufferPos(selected.window, selected.view, selected.buffer, mouse);
|
||||
Int p = ScreenSpaceToBufferPos(selected.window, selected.view, selected.buffer, mouse);
|
||||
Caret &caret = selected.view->carets[0];
|
||||
caret = SetFrontWithAnchor(caret, DocumentAnchor, p);
|
||||
|
||||
if (event.alt && event.shift) {
|
||||
Int front = GetFront(DocumentAnchor);
|
||||
XY from = PosToXY(selected.buffer, front);
|
||||
XY to = ScreenSpaceToXY(selected.window, selected.view, mouse);
|
||||
Int min_line = Min(from.y, to.y);
|
||||
Int max_line = Max(from.y, to.y);
|
||||
Int min_col = Min(from.x, to.x);
|
||||
Int max_col = Max(from.x, to.x);
|
||||
|
||||
selected.view->carets.len = 0;
|
||||
for (Int line = min_line; line <= max_line; line += 1) {
|
||||
XY left_xy = {min_col, line};
|
||||
XY right_xy = {max_col, line};
|
||||
Int left = XYToPosWithoutNL(selected.buffer, left_xy);
|
||||
Int right = XYToPosWithoutNL(selected.buffer, right_xy);
|
||||
Caret new_selection = MakeCaret(left, right);
|
||||
Add(&selected.view->carets, new_selection);
|
||||
}
|
||||
MergeCarets(selected.buffer, &selected.view->carets);
|
||||
}
|
||||
}
|
||||
|
||||
if (ResizerSelected.id != -1 && Mouse(LEFT_UP)) {
|
||||
@@ -414,8 +445,11 @@ void OnCommand(Event event) {
|
||||
DocumentSelected = active.window->id;
|
||||
|
||||
Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse);
|
||||
if (event.alt) Insert(&active.view->carets, MakeCaret(p, p), 0);
|
||||
if (!event.alt && !event.shift) active.view->carets.len = 1;
|
||||
if (event.alt) {
|
||||
Insert(&active.view->carets, MakeCaret(p, p), 0);
|
||||
} else if (!event.alt && !event.shift) {
|
||||
active.view->carets.len = 1;
|
||||
}
|
||||
|
||||
Caret &caret = active.view->carets[0];
|
||||
if (event.shift) {
|
||||
@@ -840,6 +874,9 @@ void MainLoop() {
|
||||
FrameEvents.len = 0;
|
||||
GetEventsForFrame(&FrameEvents);
|
||||
For (FrameEvents) {
|
||||
if (RecordingMacro) {
|
||||
Add(&MacroPlayback, it);
|
||||
}
|
||||
#if PLUGIN_RECORD_EVENTS
|
||||
if (it.kind != EVENT_UPDATE && !Testing) {
|
||||
Serialize(EventBuffer, &it);
|
||||
@@ -1012,8 +1049,8 @@ int main(int argc, char **argv, char **envp)
|
||||
}
|
||||
SDL_SetWindowPosition(SDLWindow, xhalf, yhalf);
|
||||
|
||||
SDL_GLContext gl_context = SDL_GL_CreateContext(SDLWindow);
|
||||
SDL_GL_MakeCurrent(SDLWindow, gl_context);
|
||||
SDL_WindowGLContext = SDL_GL_CreateContext(SDLWindow);
|
||||
SDL_GL_MakeCurrent(SDLWindow, SDL_WindowGLContext);
|
||||
SDL_ShowWindow(SDLWindow);
|
||||
|
||||
// Set icon
|
||||
@@ -1136,6 +1173,10 @@ int main(int argc, char **argv, char **envp)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PLUGIN_REMEDYBG
|
||||
QuitDebugger();
|
||||
#endif
|
||||
CleanupRender();
|
||||
SDL_DestroyWindow(SDLWindow);
|
||||
SDL_Quit();
|
||||
|
||||
|
||||
@@ -176,21 +176,23 @@ Window *SwitchWindow(int direction) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Int ScreenSpaceToBufferPos(Window *window, View *view, Buffer *buffer, Vec2I mouse) {
|
||||
XY ScreenSpaceToXY(Window *window, View *view, Vec2I mouse) {
|
||||
Vec2I mworld = mouse - window->document_rect.min + view->scroll;
|
||||
double px = (double)mworld.x / (double)window->font->char_spacing;
|
||||
double py = (double)mworld.y / (double)window->font->line_spacing;
|
||||
XY xy = {(Int)round(px), (Int)floor(py)};
|
||||
Int result = XYToPosWithoutNL(buffer, xy);
|
||||
return xy;
|
||||
}
|
||||
|
||||
Int ScreenSpaceToBufferPos(Window *window, View *view, Buffer *buffer, Vec2I mouse) {
|
||||
XY xy = ScreenSpaceToXY(window, view, mouse);
|
||||
Int result = XYToPosWithoutNL(buffer, xy);
|
||||
return result;
|
||||
}
|
||||
|
||||
Int ScreenSpaceToBufferPosErrorOutOfBounds(Window *window, View *view, Buffer *buffer, Vec2I mouse) {
|
||||
Vec2I mworld = mouse - window->document_rect.min + view->scroll;
|
||||
double px = (double)mworld.x / (double)window->font->char_spacing;
|
||||
double py = (double)mworld.y / (double)window->font->line_spacing;
|
||||
XY xy = {(Int)round(px), (Int)floor(py)};
|
||||
Int result = XYToPosErrorOutOfBounds(buffer, xy);
|
||||
XY xy = ScreenSpaceToXY(window, view, mouse);
|
||||
Int result = XYToPosErrorOutOfBounds(buffer, xy);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user