Compare commits

..

62 Commits

Author SHA1 Message Date
Krzosa Karol
20d71722ea GC temp buffers after they are 25th in history 2026-03-22 10:12:36 +01:00
Krzosa Karol
cf383c9772 Update todos 2026-03-22 10:12:18 +01:00
Krzosa Karol
82dceaca68 Missing include 2026-03-22 10:12:07 +01:00
Krzosa Karol
4f4940bcd3 Add memory usage in debug window 2026-03-22 10:11:25 +01:00
Krzosa Karol
aff3403404 Add macros 2026-03-21 17:09:40 +01:00
Krzosa Karol
b04b566681 Box mouse select 2026-03-21 16:57:19 +01:00
Krzosa Karol
aa85d9420a Fix tests 2026-03-21 16:57:07 +01:00
Krzosa Karol
4c21026842 CMD_OpenFileSystemBrowser 2026-03-21 16:56:36 +01:00
Krzosa Karol
cab882de60 Tests as part of build process and unifying with vscode keybinding scheme 2026-03-21 13:01:58 +01:00
Krzosa Karol
fadf4cd698 Move to globals SDL vars 2026-03-21 10:22:31 +01:00
Krzosa Karol
94ee03800d Basic tests and trying to fix address sanitizer errors 2026-03-21 10:10:24 +01:00
Krzosa Karol
207fc65fec Update README 2026-03-19 23:25:54 +01:00
Krzosa Karol
4ad9e6bf20 Try to bring back tests 2026-03-19 11:18:57 +01:00
Krzosa Karol
edb461bde8 Fix first build error navigation 2026-03-19 10:43:22 +01:00
Krzosa Karol
3c6153380d Fix process output truncation on POSIX 2026-03-19 10:33:56 +01:00
Krzosa Karol
7cfae9af92 add todos && build modification 2026-03-11 08:30:22 +01:00
Krzosa Karol
e1149680d9 Add todos 2026-03-11 08:27:56 +01:00
Krzosa Karol
9b2c8626d4 Fix bad memory access in ReplaceAll 2026-03-05 16:55:58 +01:00
Krzosa Karol
52c55e27d9 Fix process calling for build window 2026-03-05 08:44:02 +01:00
Krzosa Karol
56a729b617 Refactor the folder structure which only had 2 'real' modules, now it's more real, no need for splitting into folders beside the external one 2026-02-09 23:11:24 +01:00
Krzosa Karol
fde4e463ad build working SDL3 for wayland 2026-02-09 21:10:10 +01:00
Krzosa Karol
65ba886ddf build the SDL on linux 2026-02-09 19:52:42 +01:00
Krzosa Karol
84fa04e1db Fix env not gathered and not passed to child processes 2026-02-07 14:28:16 +01:00
Krzosa Karol
17f4306fc3 Update todo 2026-02-07 14:11:47 +01:00
Krzosa Karol
80df86df3d Stabilize process execution and editor cleanup 2026-02-07 11:08:29 +01:00
Krzosa Karol
7fad476b61 Misc 2026-02-05 17:28:56 +01:00
Krzosa Karol
2acf7fbb45 Drop file working in proper window 2026-02-05 17:14:00 +01:00
Krzosa Karol
36d39f9417 Improving SearchEverything, yield happens per character basis, no stalls, or multiple small buffers slowing down search 2026-02-05 09:20:40 +01:00
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
77 changed files with 2257 additions and 1529 deletions

1
.gitignore vendored
View File

@@ -18,3 +18,4 @@ te_debug
text_editor.js text_editor.js
*.map *.map
*.exe *.exe
scratch

121
README.md
View File

@@ -1,9 +1,118 @@
# te, a text editor # te
![preview](preview.jpg) 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 ![te preview](preview.jpg)
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 ## What this project is
available on your system. Just call build.sh when these are available.
`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

View File

@@ -33,7 +33,7 @@ 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
cl %flags% ../src/text_editor/text_editor.cpp -Fe:te.exe -I%sdl%/include -I../src/external/glad -I../src/ %libs% -link /SUBSYSTEM:WINDOWS /NODEFAULTLIB:LIBCMT /NODEFAULTLIB:MSVCRTD ..\data\icon.res cl %flags% ../src/text_editor.cpp -Fe:te.exe -I%sdl%/include -I../src/external/glad %libs% -link /SUBSYSTEM:WINDOWS /NODEFAULTLIB:LIBCMT /NODEFAULTLIB:MSVCRTD ..\data\icon.res
:: small util when working on the editor using the editor :: small util when working on the editor using the editor
copy te.exe te2.exe copy te.exe te2.exe

View File

@@ -1,10 +1,38 @@
#!/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
if [ -v addr ]; then echo "[address sanitizer build]"; fi
mkdir -p build
if [ ! -e "src/external/SDL" ]; then
cd src/external
git clone https://github.com/libsdl-org/SDL.git
cd SDL
git checkout release-3.2.30
# 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
cd ../../../..
fi
cd build cd build
FLAGS="-Wall -Wno-missing-braces -Wno-writable-strings -nostdlib++ -fsanitize=address -fno-exceptions -fdiagnostics-absolute-paths -g -I../src -DDEBUG_BUILD=1" I="-I../src/external/SDL/include -I../src/external/glad"
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=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

View File

@@ -1,73 +0,0 @@
! 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?
- Buffer edit history separate from caret history (tagging history blocks with carets???)
- We need regex for: [FormatCode matching, IsCode matching,
- Macros
- 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()
- 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>> <<ProjectDirectory>> template strings to Open (Then remove SEtWorkdirhere)
- :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 the equivalent of SearchOpenBuffers but for cmds like !@git grep -n "@>"
- Add Bool variable
- Guide on the first page for new users with links to configs, tutorials
Debug session:
- Report errorf - use coroutine dialogs
- Replace in render layer also
- BlockAllocator something is not working there which only showed after executing OpenCode on many files
- Some bad allocating happening in Clipboard for sure OR MAYBE BLOCK ALLOCATOR ACTION
New UI Session
- Uneditable buffers ?
- Maybe marked allocations??? So that we can associate allocations with a buffer or view and then dealloc all at the same time
- Open with seek string (open at pattern) filename:32 filename:/^Window$/
- Show what process/coroutines are running and allow to kill (active process buffer?)
- Database idea: use special buffers to store information
- Editing the buffer doesn't seem to be the slow part rather, accessing the data and putting it into the buffer (potentially hitting many different memory locations) I have a crazy idea to use buffers in order to store the names in a serialized format
- non editable buffers (raw ops ok, non-raw no op)
- DBBuffer
- Try to add Tracking Allocator and rewrite the app, free all memory at the end of the app and check all is well
FEATURE Some decl/function indexing in fuzzy format
FEATURE dump text editor state to file, restore state
- word complete
- 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)
- code sections, visual demarkation if beginning of line has a very specific text + goto next / goto prev section hotkey!
- combine glyph and selection rendering
- Test stdin writing code
- Implement shell interaction (the valid cmd lines should start with '>' or '$', user can add more lines like this to expand the command size maybe?, if we have a case where we have a line with '>' but the last line doesn't have (just a space) then it should execute?)
- drop text into window
- I think the way sublime text and we display line highlights is confusing with multiple cursors (line highlight can be confused with selection)
- make the editor replayable, store events and then replay, be careful about globals
- maybe open should return multiple options if there are many more? (like in sublime if many symbols you get a window and you choose and it automatically jumps you to the symbol in the background)
- I want a way to assign flags to buffers/views/windows from user perspective so that console window concept can be created from user space
- font cache and on demand unicode loads
- color parens, braces
- gap buffer
- 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

@@ -1,98 +0,0 @@
Basic Latin
! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~
Latin-1 Supplement
¡ ¢ £ ¤ ¥ ¦ § ¨ © ª « ¬ ­ ® ¯ ° ± ² ³ ´ µ ¶ · ¸ ¹ º » ¼ ½ ¾ ¿ À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß à á â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó ô õ ö ÷ ø ù ú û ü ý þ ÿ
Latin Extended-A
Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ ĥ Ħ ħ Ĩ ĩ Ī ī Ĭ ĭ Į į İ ı IJ ij Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ō ō Ŏ ŏ Ő ő Œ œ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ Ş ş Š š Ţ ţ Ť ť Ŧ ŧ Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ų ų Ŵ ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž ž ſ
Latin Extended-B
ƀ Ɓ Ƃ ƃ Ƅ ƅ Ɔ Ƈ ƈ Ɖ Ɗ Ƌ ƌ ƍ Ǝ Ə Ɛ Ƒ ƒ Ɠ Ɣ ƕ Ɩ Ɨ Ƙ ƙ ƚ ƛ Ɯ Ɲ ƞ Ɵ Ơ ơ Ƣ ƣ Ƥ ƥ Ʀ Ƨ ƨ Ʃ ƪ ƫ Ƭ ƭ Ʈ Ư ư Ʊ Ʋ Ƴ ƴ Ƶ ƶ Ʒ Ƹ ƹ ƺ ƻ Ƽ ƽ ƾ ƿ ǀ ǁ ǂ ǃ DŽ Dž dž LJ Lj lj NJ Nj nj Ǎ ǎ Ǐ ǐ Ǒ ǒ Ǔ ǔ Ǖ ǖ Ǘ ǘ Ǚ ǚ Ǜ ǜ ǝ Ǟ ǟ Ǡ ǡ Ǣ ǣ Ǥ ǥ Ǧ ǧ Ǩ ǩ Ǫ ǫ Ǭ ǭ Ǯ ǯ ǰ DZ Dz dz Ǵ ǵ Ǻ ǻ Ǽ ǽ Ǿ ǿ Ȁ ȁ Ȃ ȃ ...
IPA Extensions
ɐ ɑ ɒ ɓ ɔ ɕ ɖ ɗ ɘ ə ɚ ɛ ɜ ɝ ɞ ɟ ɠ ɡ ɢ ɣ ɤ ɥ ɦ ɧ ɨ ɩ ɪ ɫ ɬ ɭ ɮ ɯ ɰ ɱ ɲ ɳ ɴ ɵ ɶ ɷ ɸ ɹ ɺ ɻ ɼ ɽ ɾ ɿ ʀ ʁ ʂ ʃ ʄ ʅ ʆ ʇ ʈ ʉ ʊ ʋ ʌ ʍ ʎ ʏ ʐ ʑ ʒ ʓ ʔ ʕ ʖ ʗ ʘ ʙ ʚ ʛ ʜ ʝ ʞ ʟ ʠ ʡ ʢ ʣ ʤ ʥ ʦ ʧ ʨ
Spacing Modifier Letters
ʰ ʱ ʲ ʳ ʴ ʵ ʶ ʷ ʸ ʹ ʺ ʻ ʼ ʽ ʾ ʿ ˀ ˁ ˂ ˃ ˄ ˅ ˆ ˇ ˈ ˉ ˊ ˋ ˌ ˍ ˎ ˏ ː ˑ ˒ ˓ ˔ ˕ ˖ ˗ ˘ ˙ ˚ ˛ ˜ ˝ ˞ ˠ ˡ ˢ ˣ ˤ ˥ ˦ ˧ ˨ ˩
Combining Diacritical Marks
̀ ́ ̂ ̃ ̄ ̅ ̆ ̇ ̈ ̉ ̊ ̋ ̌ ̍ ̎ ̏ ̐ ̑ ̒ ̓ ̔ ̕ ̖ ̗ ̘ ̙ ̚ ̛ ̜ ̝ ̞ ̟ ̠ ̡ ̢ ̣ ̤ ̥ ̦ ̧ ̨ ̩ ̪ ̫ ̬ ̭ ̮ ̯ ̰ ̱ ̲ ̳ ̴ ̵ ̶ ̷ ̸ ̹ ̺ ̻ ̼ ̽ ̾ ̿ ̀ ́ ͂ ̓ ̈́ ͅ ͠ ͡
Greek
ʹ ͵ ͺ ; ΄ ΅ Ά · Έ Ή Ί Ό Ύ Ώ ΐ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω Ϊ Ϋ ά έ ή ί ΰ α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ ς σ τ υ φ χ ψ ω ϊ ϋ ό ύ ώ ϐ ϑ ϒ ϓ ϔ ϕ ϖ Ϛ Ϝ Ϟ Ϡ Ϣ ϣ Ϥ ϥ Ϧ ϧ Ϩ ϩ Ϫ ϫ Ϭ ϭ Ϯ ϯ ϰ ϱ ϲ ϳ
Cyrillic
Ё Ђ Ѓ Є Ѕ І Ї Ј Љ Њ Ћ Ќ Ў Џ А Б В Г Д Е Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я а б в г д е ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я ё ђ ѓ є ѕ і ї ј љ њ ћ ќ ў џ Ѡ ѡ Ѣ ѣ Ѥ ѥ Ѧ ѧ Ѩ ѩ Ѫ ѫ Ѭ ѭ Ѯ ѯ Ѱ ѱ Ѳ ѳ Ѵ ѵ Ѷ ѷ Ѹ ѹ Ѻ ѻ Ѽ ѽ Ѿ ѿ Ҁ ҁ ҂ ҃ ...
Armenian
Ա Բ Գ Դ Ե Զ Է Ը Թ Ժ Ի Լ Խ Ծ Կ Հ Ձ Ղ Ճ Մ Յ Ն Շ Ո Չ Պ Ջ Ռ Ս Վ Տ Ր Ց Ւ Փ Ք Օ Ֆ ՙ ՚ ՛ ՜ ՝ ՞ ՟ ա բ գ դ ե զ է ը թ ժ ի լ խ ծ կ հ ձ ղ ճ մ յ ն շ ո չ պ ջ ռ ս վ տ ր ց ւ փ ք օ ֆ և ։
Hebrew
֑ ֒ ֓ ֔ ֕ ֖ ֗ ֘ ֙ ֚ ֛ ֜ ֝ ֞ ֟ ֠ ֡ ֣ ֤ ֥ ֦ ֧ ֨ ֩ ֪ ֫ ֬ ֭ ֮ ֯ ְ ֱ ֲ ֳ ִ ֵ ֶ ַ ָ ֹ ֻ ּ ֽ ־ ֿ ׀ ׁ ׂ ׃ ׄ א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ ײ ׳ ״
Arabic
، ؛ ؟ ء آ أ ؤ إ ئ ا ب ة ت ث ج ح خ د ذ ر ز س ش ص ض ط ظ ع غ ـ ف ق ك ل م ن ه و ى ي ً ٌ ٍ َ ُ ِ ّ ْ ٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ ٪ ٫ ٬ ٭ ٰ ٱ ٲ ٳ ٴ ٵ ٶ ٷ ٸ ٹ ٺ ٻ ټ ٽ پ ٿ ڀ ځ ڂ ڃ ڄ څ چ ڇ ڈ ډ ڊ ڋ ڌ ڍ ڎ ڏ ڐ ڑ ڒ ړ ڔ ڕ ږ ڗ ژ ڙ ښ ڛ ڜ ڝ ڞ ڟ ڠ ڡ ڢ ڣ ڤ ڥ ڦ ڧ ڨ ک ڪ ګ ڬ ڭ ڮ گ ڰ ڱ ...
Devanagari
ँ ं अ आ इ ई उ ऊ ऋ ऌ ऍ ऎ ए ऐ ऑ ऒ ओ औ क ख ग घ ङ च छ ज झ ञ ट ठ ड ढ ण त थ द ध न ऩ प फ ब भ म य र ऱ ल ळ ऴ व श ष स ह ़ ऽ ा ि ी ु ू ृ ॄ ॅ ॆ े ै ॉ ॊ ो ौ ् ॐ ॑ ॒ ॓ ॔ क़ ख़ ग़ ज़ ड़ ढ़ फ़ य़ ॠ ॡ ॢ ॣ । ॥ १ २ ३ ४ ५ ६ ७ ८ ९ ॰
Bengali
ঁ ং ঃ অ আ ই ঈ উ ঊ ঋ ঌ এ ঐ ও ঔ ক খ গ ঘ ঙ চ ছ জ ঝ ঞ ট ঠ ড ঢ ণ ত থ দ ধ ন প ফ ব ভ ম য র ল শ ষ স হ ় া ি ী ু ূ ৃ ৄ ে ৈ ো ৌ ্ ৗ ড় ঢ় য় ৠ ৡ ৢ ৣ ১ ২ ৩ ৫ ৬ ৮ ৯ ৰ ৱ ৲ ৳ ৴ ৵ ৶ ৷ ৸ ৹ ৺
Gurmukhi
ਂ ਅ ਆ ਇ ਈ ਉ ਊ ਏ ਐ ਓ ਔ ਕ ਖ ਗ ਘ ਙ ਚ ਛ ਜ ਝ ਞ ਟ ਠ ਡ ਢ ਣ ਤ ਥ ਦ ਧ ਨ ਪ ਫ ਬ ਭ ਮ ਯ ਰ ਲ ਲ਼ ਵ ਸ਼ ਸ ਹ ਼ ਾ ਿ ੀ ੁ ੂ ੇ ੈ ੋ ੌ ੍ ਖ਼ ਗ਼ ਜ਼ ੜ ਫ਼ ੨ ੩ ੫ ੬ ੭ ੮ ੯ ੰ ੱ ੲ ੳ ੴ
Gujarati
ઁ ં અ આ ઇ ઈ ઉ ઊ ઋ ઍ એ ઐ ઑ ઓ ઔ ક ખ ગ ઘ ઙ ચ છ જ ઝ ઞ ટ ઠ ડ ઢ ણ ત થ દ ધ ન પ ફ બ ભ મ ય ર લ ળ વ શ ષ સ હ ઼ ઽ ા િ ી ુ ૂ ૃ ૄ ૅ ે ૈ ૉ ો ૌ ્ ૐ ૠ ૧ ૨ ૩ ૪ ૫ ૬ ૭ ૮ ૯
Oriya
ଁ ଂ ଅ ଆ ଇ ଈ ଉ ଊ ଋ ଌ ଏ ଐ ଓ ଔ କ ଖ ଗ ଘ ଙ ଚ ଛ ଜ ଝ ଞ ଟ ଡ ଢ ଣ ତ ଥ ଦ ଧ ନ ପ ଫ ବ ଭ ମ ଯ ର ଲ ଳ ଶ ଷ ସ ହ ଼ ଽ ା ି ୀ ୁ ୂ ୃ େ ୈ ୋ ୌ ୍ ୖ ୗ ଡ଼ ଢ଼ ୟ ୠ ୡ ୩ ୪ ୫ ୬ ୭ ୮ ୯ ୰
Tamil
ஂ ஃ அ ஆ இ ஈ உ ஊ எ ஏ ஐ ஒ ஓ ஔ க ங ச ஜ ஞ ட ண த ந ன ப ம ய ர ற ல ள ழ வ ஷ ஸ ஹ ா ி ீ ு ூ ெ ே ை ொ ோ ௌ ் ௗ ௧ ௨ ௩ ௪ ௫ ௬ ௭ ௮ ௯ ௰ ௱ ௲
Telugu
ః అ ఆ ఇ ఈ ఉ ఊ ఋ ఌ ఎ ఏ ఐ ఒ ఓ ఔ క ఖ గ ఘ ఙ చ ఛ జ ఝ ఞ ట ఠ డ ఢ ణ త థ ద ధ న ప ఫ బ భ మ య ర ఱ ల ళ వ శ ష స హ ా ి ీ ు ూ ృ ౄ ె ే ై ొ ో ౌ ్ ౕ ౖ ౠ ౡ ౧ ౨ ౩ ౪ ౫ ౬ ౭ ౮ ౯
Kannada
ಃ ಅ ಆ ಇ ಈ ಉ ಊ ಋ ಌ ಎ ಏ ಐ ಒ ಓ ಔ ಕ ಖ ಗ ಘ ಙ ಚ ಛ ಜ ಝ ಞ ಟ ಠ ಡ ಢ ಣ ತ ಥ ದ ಧ ನ ಪ ಫ ಬ ಭ ಮ ಯ ರ ಱ ಲ ಳ ವ ಶ ಷ ಸ ಹ ಾ ಿ ೀ ು ೂ ೃ ೄ ೆ ೇ ೈ ೊ ೋ ೌ ್ ೕ ೖ ೞ ೠ ೡ ೧ ೨ ೩ ೪ ೫ ೬ ೭ ೮ ೯
Malayalam
ഃ അ ആ ഇ ഈ ഉ ഊ ഋ ഌ എ ഏ ഐ ഒ ഓ ഔ ക ഖ ഗ ഘ ങ ച ഛ ജ ഝ ഞ ട ഡ ഢ ണ ത ഥ ദ ധ ന പ ഫ ബ ഭ മ യ ര റ ല ള ഴ വ ശ ഷ സ ഹ ാ ി ീ ു ൂ ൃ െ േ ൈ ൊ ോ ൌ ് ൗ ൠ ൡ ൧ ൨ ൩ ൪ ൫ ൬ ൮ ൯
Thai
ก ข ฃ ค ฅ ฆ ง จ ฉ ช ซ ฌ ญ ฎ ฏ ฐ ฑ ฒ ณ ด ต ถ ท ธ น บ ป ผ ฝ พ ฟ ภ ม ย ร ฤ ล ฦ ว ศ ษ ส ห ฬ อ ฮ ฯ ะ ั า ำ ิ ี ึ ื ุ ู ฺ ฿ เ แ โ ใ ไ ๅ ๆ ็ ่ ้ ๊ ๋ ์ ํ ๎ ๏ ๑ ๒ ๓ ๔ ๕ ๖ ๗ ๘ ๙ ๚ ๛
Lao
ກ ຂ ຄ ງ ຈ ຊ ຍ ດ ຕ ຖ ທ ນ ບ ປ ຜ ຝ ພ ຟ ມ ຢ ຣ ລ ວ ສ ຫ ອ ຮ ຯ ະ ັ າ ຳ ິ ີ ຶ ື ຸ ູ ົ ຼ ຽ ເ ແ ໂ ໃ ໄ ໆ ່ ້ ໊ ໋ ໌ ໍ ໑ ໒ ໓ ໔ ໕ ໖ ໗ ໘ ໙ ໜ ໝ
Tibetan
ༀ ༁ ༂ ༃ ༄ ༅ ༆ ༇ ༈ ༉ ༊ ་ ༌ ། ༎ ༏ ༐ ༑ ༒ ༓ ༔ ༕ ༖ ༗ ༘ ༙ ༚ ༛ ༜ ༝ ༞ ༟ ༠ ༡ ༢ ༣ ༤ ༥ ༦ ༧ ༨ ༩ ༪ ༫ ༬ ༭ ༮ ༯ ༰ ༱ ༲ ༳ ༴ ༵ ༶ ༷ ༸ ༹ ༺ ༻ ༼ ༽ ༾ ༿ ཀ ཁ ག གྷ ང ཅ ཆ ཇ ཉ ཊ ཋ ཌ ཌྷ ཎ ཏ ཐ ད དྷ ན པ ཕ བ བྷ མ ཙ ཚ ཛ ཛྷ ཝ ཞ ཟ འ ཡ ར ལ ཤ ཥ ས ཧ ཨ ཀྵ ཱ ི ཱི ུ ཱུ ྲྀ ཷ ླྀ ཹ ེ ཻ ོ ཽ ཾ ཿ ྀ ཱྀ ྂ ྃ ྄ ྅ ྆ ྇ ...
Georgian
Ⴀ Ⴁ Ⴂ Ⴃ Ⴄ Ⴅ Ⴆ Ⴇ Ⴈ Ⴉ Ⴊ Ⴋ Ⴌ Ⴍ Ⴎ Ⴏ Ⴐ Ⴑ Ⴒ Ⴓ Ⴔ Ⴕ Ⴖ Ⴗ Ⴘ Ⴙ Ⴚ Ⴛ Ⴜ Ⴝ Ⴞ Ⴟ Ⴠ Ⴡ Ⴢ Ⴣ Ⴤ Ⴥ ა ბ გ დ ე ვ ზ თ ი კ ლ მ ნ ო პ ჟ რ ს ტ უ ფ ქ ღ შ ჩ ც ძ წ ჭ ხ ჯ ჰ ჱ ჲ ჳ ჴ ჵ ჶ ჻
Hangul Jamo
ᄀ ᄁ ᄂ ᄃ ᄄ ᄅ ᄆ ᄇ ᄈ ᄉ ᄊ ᄋ ᄌ ᄍ ᄎ ᄏ ᄐ ᄑ ᄒ ᄓ ᄔ ᄕ ᄖ ᄗ ᄘ ᄙ ᄚ ᄛ ᄜ ᄝ ᄞ ᄟ ᄠ ᄡ ᄢ ᄣ ᄤ ᄥ ᄦ ᄧ ᄨ ᄩ ᄪ ᄫ ᄬ ᄭ ᄮ ᄯ ᄰ ᄱ ᄲ ᄳ ᄴ ᄵ ᄶ ᄷ ᄸ ᄹ ᄺ ᄻ ᄼ ᄽ ᄾ ᄿ ᅀ ᅁ ᅂ ᅃ ᅄ ᅅ ᅆ ᅇ ᅈ ᅉ ᅊ ᅋ ᅌ ᅍ ᅎ ᅏ ᅐ ᅑ ᅒ ᅓ ᅔ ᅕ ᅖ ᅗ ᅘ ᅙ ᅡ ᅢ ᅣ ᅤ ᅥ ᅦ ᅧ ᅨ ᅩ ᅪ ᅫ ᅬ ᅭ ᅮ ᅯ ᅰ ᅱ ᅲ ᅳ ᅴ ᅵ ᅶ ᅷ ᅸ ᅹ ᅺ ᅻ ᅼ ᅽ ᅾ ᅿ ᆀ ᆁ ᆂ ᆃ ᆄ ...
Latin Extended Additional
Ḁ ḁ Ḃ ḃ Ḅ ḅ Ḇ ḇ Ḉ ḉ Ḋ ḋ Ḍ ḍ Ḏ ḏ Ḑ ḑ Ḓ ḓ Ḕ ḕ Ḗ ḗ Ḙ ḙ Ḛ ḛ Ḝ ḝ Ḟ ḟ Ḡ ḡ Ḣ ḣ Ḥ ḥ Ḧ ḧ Ḩ ḩ Ḫ ḫ Ḭ ḭ Ḯ ḯ Ḱ ḱ Ḳ ḳ Ḵ ḵ Ḷ ḷ Ḹ ḹ Ḻ ḻ Ḽ ḽ Ḿ ḿ Ṁ ṁ Ṃ ṃ Ṅ ṅ Ṇ ṇ Ṉ ṉ Ṋ ṋ Ṍ ṍ Ṏ ṏ Ṑ ṑ Ṓ ṓ Ṕ ṕ Ṗ ṗ Ṙ ṙ Ṛ ṛ Ṝ ṝ Ṟ ṟ Ṡ ṡ Ṣ ṣ Ṥ ṥ Ṧ ṧ Ṩ ṩ Ṫ ṫ Ṭ ṭ Ṯ ṯ Ṱ ṱ Ṳ ṳ Ṵ ṵ Ṷ ṷ Ṹ ṹ Ṻ ṻ Ṽ ṽ Ṿ ṿ ...
Greek Extended
ἀ ἁ ἂ ἃ ἄ ἅ ἆ ἇ Ἀ Ἁ Ἂ Ἃ Ἄ Ἅ Ἆ Ἇ ἐ ἑ ἒ ἓ ἔ ἕ Ἐ Ἑ Ἒ Ἓ Ἔ Ἕ ἠ ἡ ἢ ἣ ἤ ἥ ἦ ἧ Ἠ Ἡ Ἢ Ἣ Ἤ Ἥ Ἦ Ἧ ἰ ἱ ἲ ἳ ἴ ἵ ἶ ἷ Ἰ Ἱ Ἲ Ἳ Ἴ Ἵ Ἶ Ἷ ὀ ὁ ὂ ὃ ὄ ὅ Ὀ Ὁ Ὂ Ὃ Ὄ Ὅ ὐ ὑ ὒ ὓ ὔ ὕ ὖ ὗ Ὑ Ὓ Ὕ Ὗ ὠ ὡ ὢ ὣ ὤ ὥ ὦ ὧ Ὠ Ὡ Ὢ Ὣ Ὤ Ὥ Ὦ Ὧ ὰ ά ὲ έ ὴ ή ὶ ί ὸ ό ὺ ύ ὼ ώ ᾀ ᾁ ᾂ ᾃ ᾄ ᾅ ᾆ ᾇ ᾈ ᾉ ᾊ ᾋ ᾌ ᾍ ...
General Punctuation
  — ― ‖ ‗ “ ” „ ‟ † ‡ • ‣ ‥ … ‧

 ‰ ‱ ″ ‴ ‶ ‷ ‸ ※ ‼ ‽ ‾ ‿ ⁀ ⁅ ⁆
Superscripts and Subscripts
⁰ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁼ ⁽ ⁾ ⁿ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₊ ₋ ₌ ₍ ₎
Currency Symbols
₠ ₡ ₢ ₣ ₤ ₥ ₦ ₧ ₨ ₩ ₪ ₫
Combining Marks for Symbols
⃐ ⃑ ⃒ ⃓ ⃔ ⃕ ⃖ ⃗ ⃘ ⃙ ⃚ ⃛ ⃜ ⃝ ⃞ ⃟ ⃠ ⃡
Letterlike Symbols
℀ ℁ ℃ ℄ ℅ ℆ ℇ ℈ ℉ № ℗ ℘ ℞ ℟ ℠ ℡ ™ ℣ ℥ Ω ℧ ℵ ℶ ℷ ℸ
Number Forms
⅓ ⅔ ⅕ ⅖ ⅗ ⅘ ⅙ ⅚ ⅛ ⅜ ⅝ ⅞ ⅟ Ⅱ Ⅲ Ⅳ Ⅵ Ⅶ Ⅷ Ⅸ Ⅺ Ⅻ ⅱ ⅲ ⅳ ⅵ ⅶ ⅷ ⅸ ⅺ ⅻ ⅿ ↀ ↁ ↂ
Arrows
← ↑ → ↓ ↔ ↕ ↖ ↗ ↘ ↙ ↚ ↛ ↜ ↝ ↞ ↟ ↠ ↡ ↢ ↣ ↤ ↥ ↦ ↧ ↨ ↩ ↪ ↫ ↬ ↭ ↮ ↯ ↰ ↱ ↲ ↳ ↴ ↵ ↶ ↷ ↸ ↹ ↺ ↻ ↼ ↽ ↾ ↿ ⇀ ⇁ ⇂ ⇃ ⇄ ⇅ ⇆ ⇇ ⇈ ⇉ ⇊ ⇋ ⇌ ⇍ ⇎ ⇏ ⇐ ⇑ ⇒ ⇓ ⇔ ⇕ ⇖ ⇗ ⇘ ⇙ ⇚ ⇛ ⇜ ⇝ ⇞ ⇟ ⇠ ⇡ ⇢ ⇣ ⇤ ⇥ ⇦ ⇧ ⇨ ⇩ ⇪
Mathematical Operators
∀ ∁ ∂ ∃ ∄ ∅ ∆ ∇ ∈ ∉ ∊ ∋ ∌ ∍ ∎ ∏ ∐ ∑ ∓ ∔ ∘ ∙ √ ∛ ∜ ∝ ∞ ∟ ∠ ∡ ∢ ∤ ∥ ∦ ∧ ∫ ∬ ∭ ∮ ∯ ∰ ∱ ∲ ∳ ∴ ∵ ∷ ∸ ∹ ∺ ∻ ∽ ∾ ∿ ≀ ≁ ≂ ≃ ≄ ≅ ≆ ≇ ≈ ≉ ≊ ≋ ≌ ≍ ≎ ≏ ≐ ≑ ≒ ≓ ≔ ≕ ≖ ≗ ≘ ≙ ≚ ≛ ≜ ≝ ≞ ≟ ≠ ≡ ≢ ≣ ≤ ≥ ≦ ≧ ≨ ≩ ≪ ≫ ≬ ≭ ≮ ≯ ≰ ≱ ≲ ≳ ≴ ≵ ≶ ≷ ≸ ≹ ≺ ≻ ≼ ≽ ≾ ≿ ...
Miscellaneous Technical
⌀ ⌂ ⌃ ⌄ ⌅ ⌆ ⌇ ⌈ ⌉ ⌊ ⌋ ⌌ ⌍ ⌎ ⌏ ⌐ ⌑ ⌒ ⌓ ⌔ ⌕ ⌖ ⌗ ⌘ ⌙ ⌚ ⌛ ⌜ ⌝ ⌞ ⌟ ⌠ ⌡ ⌢ ⌣ ⌤ ⌥ ⌦ ⌧ ⌨ 〈 〉 ⌫ ⌬ ⌭ ⌮ ⌯ ⌰ ⌱ ⌲ ⌳ ⌴ ⌵ ⌶ ⌷ ⌸ ⌹ ⌺ ⌻ ⌼ ⌽ ⌾ ⌿ ⍀ ⍁ ⍂ ⍃ ⍄ ⍅ ⍆ ⍇ ⍈ ⍉ ⍊ ⍋ ⍌ ⍍ ⍎ ⍏ ⍐ ⍑ ⍒ ⍓ ⍔ ⍕ ⍖ ⍗ ⍘ ⍙ ⍚ ⍛ ⍜ ⍝ ⍞ ⍟ ⍠ ⍡ ⍢ ⍣ ⍤ ⍥ ⍦ ⍧ ⍨ ⍩ ⍪ ⍫ ⍬ ⍭ ⍮ ⍯ ⍰ ⍱ ⍲ ⍵ ⍶ ⍷ ⍸ ⍹
Control Pictures
␀ ␁ ␂ ␃ ␄ ␅ ␆ ␇ ␈ ␉ ␊ ␋ ␌ ␍ ␎ ␏ ␐ ␑ ␒ ␓ ␔ ␕ ␖ ␗ ␘ ␙ ␚ ␛ ␜ ␝ ␞ ␟ ␠ ␡ ␢ ␣ ␤
Optical Character Recognition
⑀ ⑁ ⑂ ⑃ ⑄ ⑅ ⑆ ⑇ ⑈ ⑉ ⑊
Enclosed Alphanumerics
① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ ⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛ ⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ...
Box Drawing
─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏ ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟ ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯ ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿ ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏ ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟ ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯ ╰ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
Block Elements
▀ ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ ▉ ▊ ▋ ▌ ▍ ▎ ▏ ▐ ░ ▒ ▓ ▔ ▕
Geometric Shapes
■ □ ▢ ▣ ▤ ▥ ▦ ▧ ▨ ▩ ▪ ▫ ▬ ▭ ▮ ▯ ▰ ▱ ▲ △ ▴ ▵ ▶ ▷ ▸ ▹ ► ▻ ▼ ▽ ▾ ▿ ◀ ◁ ◂ ◃ ◄ ◅ ◆ ◇ ◈ ◉ ◊ ○ ◌ ◍ ◎ ● ◐ ◑ ◒ ◓ ◔ ◕ ◖ ◗ ◘ ◙ ◚ ◛ ◜ ◝ ◞ ◟ ◠ ◡ ◢ ◣ ◤ ◥ ◦ ◧ ◨ ◩ ◪ ◫ ◬ ◭ ◮ ◯
Miscellaneous Symbols
☀ ☁ ☂ ☃ ☄ ★ ☆ ☇ ☈ ☉ ☊ ☋ ☌ ☍ ☎ ☏ ☐ ☑ ☒ ☓ ☚ ☛ ☜ ☝ ☞ ☟ ☠ ☡ ☢ ☣ ☤ ☥ ☦ ☧ ☨ ☩ ☪ ☫ ☬ ☭ ☮ ☯ ☰ ☱ ☲ ☳ ☴ ☵ ☶ ☷ ☸ ☹ ☺ ☻ ☼ ☽ ☾ ☿ ♀ ♁ ♂ ♃ ♄ ♅ ♆ ♇ ♈ ♉ ♊ ♋ ♌ ♍ ♎ ♏ ♐ ♑ ♒ ♓ ♔ ♕ ♖ ♗ ♘ ♙ ♚ ♛ ♜ ♝ ♞ ♟ ♠ ♡ ♢ ♣ ♤ ♥ ♦ ♧ ♨ ♩ ♪ ♫ ♬ ♭ ♮ ♯
Dingbats
✁ ✂ ✃ ✄ ✆ ✇ ✈ ✉ ✌ ✍ ✎ ✏ ✐ ✑ ✒ ✓ ✔ ✕ ✖ ✗ ✘ ✙ ✚ ✛ ✜ ✝ ✞ ✟ ✠ ✡ ✢ ✣ ✤ ✥ ✦ ✧ ✩ ✪ ✫ ✬ ✭ ✮ ✯ ✰ ✱ ✲ ✳ ✴ ✵ ✶ ✷ ✸ ✹ ✺ ✻ ✼ ✽ ✾ ✿ ❀ ❁ ❂ ❃ ❄ ❅ ❆ ❇ ❈ ❉ ❊ ❋ ❍ ❏ ❐ ❑ ❒ ❖ ❘ ❙ ❚ ❛ ❜ ❝ ❞ ❡ ❢ ❣ ❤ ❥ ❦ ❧ ❶ ❷ ❸ ❹ ❺ ❻ ❼ ❽ ❾ ❿ ➀ ➁ ➂ ➃ ➄ ➅ ➆ ➇ ➈ ➉ ➊ ➋ ➌ ➍ ➎ ➏ ➐ ➑ ➒ ➓ ➔ ➘ ➙ ➚ ➛ ➜ ➝ ...
CJK Symbols and Punctuation
  、 。 〃 〄 々 〆 〈 〉 《 》 「 」 『 』 【 】 〒 〓 〖 〗 〘 〙 〚 〛 〜 〝 〞 〟 〠 〡 〢 〣 〤 〥 〦 〧 〨 〩 〪 〫 〬 〭 〮 〯 〰 〱 〲 〴 〵 〶 〷 〿
Hiragana
ぁ あ ぃ い ぅ う ぇ え ぉ お か が き ぎ く ぐ け げ こ ご さ ざ し じ す ず せ ぜ そ ぞ た だ ち ぢ っ つ づ て で と ど な に ぬ ね の は ば ぱ ひ び ぴ ふ ぶ ぷ へ べ ぺ ほ ぼ ぽ ま み む め も ゃ や ゅ ゆ ょ よ ら り る れ ろ ゎ わ ゐ ゑ を ん ゔ ゙ ゚ ゛ ゜ ゝ ゞ
Katakana
ァ ア ィ イ ゥ ウ ェ エ ォ オ カ ガ キ ギ ク グ ケ ゲ コ ゴ サ ザ シ ジ ス ズ セ ゼ ソ ゾ タ ダ チ ヂ ッ ツ ヅ テ デ ト ド ナ ニ ヌ ネ ハ バ パ ヒ ビ ピ フ ブ プ ヘ ベ ペ ホ ボ ポ マ ミ ム メ モ ャ ヤ ュ ユ ョ ヨ ラ リ ル レ ロ ヮ ワ ヰ ヱ ヲ ン ヴ ヵ ヶ ヷ ヸ ヹ ヺ ・ ー ヽ ヾ
Bopomofo
ㄅ ㄆ ㄇ ㄈ ㄉ ㄊ ㄋ ㄌ ㄍ ㄎ ㄏ ㄐ ㄑ ㄒ ㄓ ㄔ ㄕ ㄖ ㄗ ㄘ ㄙ ㄚ ㄛ ㄜ ㄝ ㄞ ㄟ ㄠ ㄡ ㄢ ㄣ ㄤ ㄥ ㄦ ㄧ ㄨ ㄩ ㄪ ㄫ ㄬ

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

View File

@@ -165,7 +165,7 @@ Slice<T> GetSlice(Slice<T> &arr, int64_t first_index = 0, int64_t one_past_last_
} }
// Make arrays resize on every item // Make arrays resize on every item
#define ARRAY_DEBUG BUILD_SLOW #define ARRAY_DEBUG 0
#if ARRAY_DEBUG #if ARRAY_DEBUG
#define ARRAY_IF_DEBUG_ELSE(IF, ELSE) IF #define ARRAY_IF_DEBUG_ELSE(IF, ELSE) IF
#else #else
@@ -211,12 +211,11 @@ void Reserve(Array<T> *arr, int64_t size) {
T *new_data = AllocArray(arr->allocator, T, size); T *new_data = AllocArray(arr->allocator, T, size);
Assert(new_data); Assert(new_data);
memcpy(new_data, arr->data, arr->len * sizeof(T)); MemoryCopy(new_data, arr->data, arr->len * sizeof(T));
Dealloc(arr->allocator, arr->data); if (arr->data) Dealloc(arr->allocator, arr->data);
arr->data = new_data; arr->data = new_data;
arr->cap = size; arr->cap = size;
} }
} }
template <class T> template <class T>
@@ -493,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

@@ -85,6 +85,14 @@
#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
@@ -93,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)
@@ -116,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;
@@ -131,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;
@@ -246,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

@@ -18,16 +18,20 @@ typedef void OSErrorReport(const char *, ...);
#include <poll.h> #include <poll.h>
#include <execinfo.h> #include <execinfo.h>
#include <backtrace.h> #include <backtrace.h>
#include <sys/resource.h>
API void (*Error)(const char *, ...); 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 +50,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 +107,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 +259,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 +269,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 +529,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 +722,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(Allocator allocator, String data) {
Scratch scratch(allocator);
#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(allocator, temp16);
#else
String temp_directory = "/tmp";
#endif
String temp_filename = Format(allocator, "%S/temp%llu", temp_directory, GetTimeNanos());
bool done = WriteFile(temp_filename, data);
Assert(done);
return temp_filename;
}

View File

@@ -32,23 +32,11 @@ bool IsFile(String path);
String GetWorkingDir(Allocator arena); String GetWorkingDir(Allocator arena);
bool IsAbsolute(String path); bool IsAbsolute(String path);
int64_t GetFileModTime(String file); int64_t GetFileModTime(String file);
String WriteTempFile(Allocator allocator, String data);
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

@@ -64,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;
@@ -309,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));
} }
@@ -425,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;
@@ -472,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;
@@ -908,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;
@@ -1167,6 +1152,7 @@ API void InitBuffer(Allocator allocator, Buffer *buffer, BufferID id = {}, Strin
if (!buffer->no_line_starts) { if (!buffer->no_line_starts) {
Add(&buffer->line_starts, (Int)0); Add(&buffer->line_starts, (Int)0);
} }
if (DebugTraceBufferInits) printf("InitBuffer %.*s %p\n", (int)name.len, name.data, buffer->data);
} }
API void DeinitBuffer(Buffer *buffer) { API void DeinitBuffer(Buffer *buffer) {
@@ -1176,6 +1162,7 @@ API void DeinitBuffer(Buffer *buffer) {
Dealloc(&buffer->line_starts); Dealloc(&buffer->line_starts);
DeallocHistoryArray(&buffer->undo_stack); DeallocHistoryArray(&buffer->undo_stack);
DeallocHistoryArray(&buffer->redo_stack); DeallocHistoryArray(&buffer->redo_stack);
if (DebugTraceBufferInits) printf("DeinitBuffer %.*s %p\n", (int)buffer->name.len, buffer->name.data, buffer->data);
} }
// Indexing starts from 0 not 1 because this routine creates also the zero buffer // Indexing starts from 0 not 1 because this routine creates also the zero buffer
@@ -1256,7 +1243,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');
@@ -1271,7 +1258,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);
@@ -1463,7 +1450,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;
} }
@@ -1572,7 +1559,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);
}
} }
} }

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,31 +67,25 @@ 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();
}
Appendf(LogView, "%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);
Appendf(LogView, "%S\n", string); Appendf(LogView, "%S\n", string);
} }
void ReportWarningf(const char *fmt, ...) { void ReportErrorf(const char *fmt, ...) {
ErrorCount += 1; ErrorCount += 1;
Scratch scratch; Scratch scratch;
STRING_FORMAT(scratch, fmt, string); STRING_FORMAT(scratch, fmt, string);
if (BreakOnError) { if (BreakOnError) {
BREAK(); BREAK();
} }
Appendf(LogView, "%S\n", string);
if (LogView) {
Appendf(LogView, "%S\n", string);
} else {
printf("%.*s\n", (int)string.len, string.data);
}
} }
void CMD_CenterView() { void CMD_CenterView() {
@@ -131,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;
} }
@@ -176,15 +141,6 @@ void ApplyFormattingTool(Buffer *buffer, String tool) {
} }
} }
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);
}
}
}
// @todo: plugin_languages ? and then branch out language_cpp ... // @todo: plugin_languages ? and then branch out language_cpp ...
void CMD_FormatSelection() { void CMD_FormatSelection() {
Scratch scratch; Scratch scratch;
@@ -249,6 +205,55 @@ void CMD_OpenLogs() {
Open(LogBuffer->name); Open(LogBuffer->name);
} RegisterCommand(CMD_OpenLogs, "", "Opens the text editor logs and clear error counter"); } RegisterCommand(CMD_OpenLogs, "", "Opens the text editor logs and clear error counter");
void CMD_OpenScratch() {
ErrorCount = 0;
Buffer *buffer = GetBuffer(NullBufferID);
Open(buffer->name);
} RegisterCommand(CMD_OpenScratch, "", "Opens the scratch buffer");
void CMD_Errors() { void CMD_Errors() {
CMD_OpenLogs(); CMD_OpenLogs();
} RegisterCommand(CMD_Errors, "", "Opens the text editor logs and clear error counter"); } 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-semicolon", "Find /* and */ and select the content in between");

View File

@@ -40,9 +40,13 @@ void ClipboardCopy(View *view) {
// First, if there is no selection - select the entire line // First, if there is no selection - select the entire line
For(view->carets) { For(view->carets) {
if (GetSize(it.range) == 0) { if (GetSize(it.range) == 0) {
Int line = PosToLine(buffer, it.range.min); Int line = PosToLine(buffer, it.range.min);
Range line_range = GetLineRange(buffer, line); Int eof = 0;
it.range = line_range; Range line_range = GetLineRange(buffer, line, &eof);
it.range = line_range;
if (eof) {
it.range.min = ClampBottom(0ll, it.range.min - 1ll);
}
} }
} }
@@ -80,9 +84,9 @@ 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, EndEdit_KillSelection); EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
} }

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,17 +54,18 @@ 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;
} }
} }
@@ -73,13 +80,14 @@ void CoUpdate(Event *event) {
#endif #endif
double took = GetTimeSeconds() - start; 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; break;
} }
} }
} }
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);

220
src/data_desc.cpp Normal file
View File

@@ -0,0 +1,220 @@
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) {
Unused(lex);
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,21 +208,22 @@ void DrawWindow(Window *window, Event &event) {
} }
// Underline word under mouse cursor // Underline word under mouse cursor
Caret caret = view->carets[0]; if (1) {
Vec2I mouse = MouseVec2I(); Caret caret = view->carets[0];
bool mouse_in_document = AreOverlapping(mouse, window->document_rect); Vec2I mouse = MouseVec2I();
if (mouse_in_document) { bool mouse_in_document = AreOverlapping(mouse, window->document_rect);
View *view = GetView(window->active_view); if (mouse_in_document) {
Buffer *buffer = GetBuffer(view->active_buffer); Int p = ScreenSpaceToBufferPosErrorOutOfBounds(window, view, buffer, mouse);
Int p = ScreenSpaceToBufferPosErrorOutOfBounds(window, view, buffer, mouse); if (p != -1) {
if (p != -1) { Range range = EncloseLoadWord(buffer, p);
Range range = EncloseLoadWord(buffer, p); if (InBounds(caret.range, p)) range = caret.range;
if (InBounds(caret.range, p)) range = caret.range; DrawUnderline(window, view, buffer, range, MouseUnderlineColor, 2);
DrawUnderline(window, view, buffer, range, MouseUnderlineColor, 2); }
} }
} }
if (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,18 +347,18 @@ struct { String string; SDL_Keycode value; } SDLKeycodeConversionTable[] = {
}; };
void FillEventWithBasicData(Event *event) { void FillEventWithBasicData(Event *event) {
if (OS_WINDOWS) { #if OS_WINDOWS
if (GetKeyState(VK_SHIFT) & 0x8000) event->shift = 1; if (GetKeyState(VK_SHIFT) & 0x8000) event->shift = 1;
if (GetKeyState(VK_CONTROL) & 0x8000) event->ctrl = 1; if (GetKeyState(VK_CONTROL) & 0x8000) event->ctrl = 1;
if (GetKeyState(VK_MENU) & 0x8000) event->alt = 1; if (GetKeyState(VK_MENU) & 0x8000) event->alt = 1;
if (GetKeyState(VK_LWIN) & 0x8000) event->super = 1; if (GetKeyState(VK_LWIN) & 0x8000) event->super = 1;
} else { #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);
@@ -450,10 +450,12 @@ Event TranslateSDLEvent(SDL_Event *input_event) {
} break; } break;
case SDL_EVENT_DROP_FILE: { case SDL_EVENT_DROP_FILE: {
event.kind = EVENT_DROP_FILE; event.kind = EVENT_DROP_FILE;
SDL_DropEvent &b = input_event->drop; SDL_DropEvent &b = input_event->drop;
String string = b.data; String string = b.data;
event.text = Intern(&GlobalInternTable, string).data; event.text = Intern(&GlobalInternTable, string).data;
event.xmouse = (int16_t)roundf(DPIScale * b.x);
event.ymouse = (int16_t)roundf(DPIScale * b.y);
} break; } break;
default: { default: {
}; };
@@ -485,6 +487,7 @@ void GetEventsForFrame(Array<Event> *events) {
For (EventPlayback) { For (EventPlayback) {
Add(events, it); Add(events, it);
} }
EventPlayback.len = 0;
SDL_Event event; SDL_Event event;
if (WaitForEventsState) { if (WaitForEventsState) {
@@ -499,10 +502,10 @@ void GetEventsForFrame(Array<Event> *events) {
} }
if (events->len == 0) { if (events->len == 0) {
Event event = {}; Event ev = {};
FillEventWithBasicData(&event); FillEventWithBasicData(&ev);
event.kind = EVENT_UPDATE; ev.kind = EVENT_UPDATE;
Add(events, event); Add(events, ev);
} }
Assert(events->len); Assert(events->len);

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

@@ -1,8 +1,8 @@
float NewFuzzyRate(String16 s, String16 p) { // float NewFuzzyRate(String16 s, String16 p) {
float score = 0; // float score = 0;
// try to do this: https://github.com/junegunn/fzf/blob/master/src/algo/algo.go // // try to do this: https://github.com/junegunn/fzf/blob/master/src/algo/algo.go
return score; // return score;
} // }
float FuzzyRate(String16 s, String16 p) { float FuzzyRate(String16 s, String16 p) {
float score = 0; float score = 0;

View File

@@ -1,8 +1,10 @@
SDL_Window *SDLWindow; SDL_Window *SDLWindow;
SDL_GLContext SDL_WindowGLContext;
SDL_Cursor *SDL_MouseCursor;
SDL_SystemCursor SDL_MouseCursorLastID;
bool IsInFullscreen; bool IsInFullscreen;
int FullScreenSizeX, FullScreenSizeY; int FullScreenSizeX, FullScreenSizeY;
int FullScreenPositionX, FullScreenPositionY; int FullScreenPositionX, FullScreenPositionY;
bool Testing = false;
bool AppIsRunning = true; bool AppIsRunning = true;
bool WaitForEventsState = true; bool WaitForEventsState = true;
bool RunGCThisFrame; bool RunGCThisFrame;
@@ -10,22 +12,10 @@ bool SearchCaseSensitive = false;
bool SearchWordBoundary = false; bool SearchWordBoundary = false;
bool BreakOnError = false; bool BreakOnError = false;
Int ErrorCount; Int ErrorCount;
#if DEBUG_BUILD bool DebugTraceBufferInits = false;
String16 InitialScratchContent = uR"==(:OpenProject bool Testing = false;
C:/text_editor/src/text_editor/text_editor.cpp
0
1
2
3
4
5
6)==";
#else
String16 InitialScratchContent;
#endif
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
@@ -75,6 +65,8 @@ Vec2I MouseMiddleAnchor;
RandomSeed UniqueBufferNameSeed = {}; RandomSeed UniqueBufferNameSeed = {};
Array<Event> EventPlayback; Array<Event> EventPlayback;
Array<Event> MacroPlayback;
bool RecordingMacro = false;
BlockArena Perm; BlockArena Perm;
// clipboard // clipboard
@@ -109,8 +101,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};
@@ -182,20 +180,22 @@ 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 // PLUGIN_BUILD_WINDOW
RegisterVariable(String, Build1OnWindows, "build.bat slow"); RegisterVariable(String, Build1OnWindows, "build.bat");
RegisterVariable(String, Build1OnUnix, "sh build.sh slow"); RegisterVariable(String, Build1OnUnix, "sh build.sh");
RegisterVariable(String, Build2OnWindows, "build.bat"); RegisterVariable(String, Build2OnWindows, "build.bat release");
RegisterVariable(String, Build2OnUnix, "sh build.sh"); RegisterVariable(String, Build2OnUnix, "sh build.sh release");
RegisterVariable(String, Build3OnWindows, "build.bat release"); RegisterVariable(String, Build3OnWindows, "build.bat slow");
RegisterVariable(String, Build3OnUnix, "sh build.sh release"); RegisterVariable(String, Build3OnUnix, "sh build.sh slow");
RegisterVariable(String, Build4OnWindows, "build.bat release"); RegisterVariable(String, Build4OnWindows, "build.bat");
RegisterVariable(String, Build4OnUnix, "sh build.sh release"); RegisterVariable(String, Build4OnUnix, "sh build.sh");
// PLUGIN_LOAD_VCVARS // PLUGIN_LOAD_VCVARS

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

View File

@@ -27,17 +27,17 @@ void CMD_KillSelectedLines() {
void CMD_IndentSelectedLines() { void CMD_IndentSelectedLines() {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
IndentSelectedLines(active.view); IndentSelectedLines(active.view);
} RegisterCommand(CMD_IndentSelectedLines, "ctrl-rightbracket", ""); } RegisterCommand(CMD_IndentSelectedLines, "ctrl-rightbracket | tab", "");
void CMD_DedentSelectedLines() { void CMD_DedentSelectedLines() {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
IndentSelectedLines(active.view, true); IndentSelectedLines(active.view, true);
} RegisterCommand(CMD_DedentSelectedLines, "ctrl-leftbracket", ""); } RegisterCommand(CMD_DedentSelectedLines, "ctrl-leftbracket | shift-tab", "");
void CMD_DuplicateLineDown() { void CMD_DuplicateLineDown() {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
DuplicateLine(active.view, DIR_DOWN); DuplicateLine(active.view, DIR_DOWN);
} RegisterCommand(CMD_DuplicateLineDown, "ctrl-alt-down", ""); } RegisterCommand(CMD_DuplicateLineDown, "ctrl-shift-alt-down", "");
void CMD_CreateCursorDown() { void CMD_CreateCursorDown() {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
@@ -72,7 +72,7 @@ void CMD_MoveDown() {
void CMD_DuplicateLineUp() { void CMD_DuplicateLineUp() {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
DuplicateLine(active.view, DIR_UP); DuplicateLine(active.view, DIR_UP);
} RegisterCommand(CMD_DuplicateLineUp, "ctrl-alt-up", ""); } RegisterCommand(CMD_DuplicateLineUp, "ctrl-shift-alt-up", "");
void CMD_CreateCursorUp() { void CMD_CreateCursorUp() {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
@@ -234,6 +234,12 @@ void CMD_NewLine() {
IndentedNewLine(active.view); IndentedNewLine(active.view);
} RegisterCommand(CMD_NewLine, "enter | shift-enter", ""); } 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() { void CMD_CreateCaretOnNextFind() {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
String16 string = GetString(active.buffer, active.view->carets[0].range); 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.len = 1;
active.view->carets[0] = MakeCaret(GetFront(active.view->carets[0])); 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"); } 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");

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,44 +32,52 @@ 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 windows_cmd, String unix_cmd, String working_dir = ProjectDirectory) { BSet ExecBuild(String windows_cmd, String unix_cmd, String working_dir = ProjectFolder) {
SaveAll(); 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);
if (OS_WINDOWS) { {
Exec(build.view->id, true, windows_cmd, working_dir); ExecArgs args = ShellArgs(scratch, build.view->id, OS_WINDOWS ? windows_cmd : unix_cmd, working_dir);
} else { args.scroll_to_end = true;
Exec(build.view->id, true, unix_cmd, working_dir); 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 = -1;
build.window->visible = true; build.window->visible = true;
return build; return build;
} }
void CMD_Build1() { void CMD_Build1() {
ExecBuild(Build1OnWindows, Build1OnUnix); ExecBuild(Build1OnWindows, Build1OnUnix);
} RegisterCommand(CMD_Build1, "f1", "Run Build1OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer"); } RegisterCommand(CMD_Build1, "ctrl-b", "Run Build1OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer");
void CMD_Build2() { void CMD_Build2() {
ExecBuild(Build2OnWindows, Build2OnUnix); ExecBuild(Build2OnWindows, Build2OnUnix);
} RegisterCommand(CMD_Build2, "f2", "Run Build2OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer"); } RegisterCommand(CMD_Build2, "alt-b", "Run Build2OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer");
void CMD_Build3() { void CMD_Build3() {
ExecBuild(Build3OnWindows, Build3OnUnix); ExecBuild(Build3OnWindows, Build3OnUnix);
} RegisterCommand(CMD_Build3, "f3", "Run Build3OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer"); } 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() { void CMD_Build4() {
ExecBuild(Build4OnWindows, Build4OnUnix); ExecBuild(Build4OnWindows, Build4OnUnix);
} RegisterCommand(CMD_Build4, "f4", "Run Build4OnWindows or OnUnix in working directory, output is printed in a popup console and a special build buffer"); } 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);
String 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); // @todo: this calls for language_cpp, language_c, language_python etc.
String 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); String windows_command = "";
String unix_command = "";
if (EndsWith(primary.buffer->name, ".py")) {
unix_command = windows_command = Format(scratch, "python %S", primary.buffer->name);
} else {
windows_command = Format(scratch, "cl %S -Fe:cfile.exe /Zi /FC /nologo /WX /W3 /wd4200 /wd4334 /diagnostics:column && cfile.exe && del cfile.*", primary.buffer->name);
unix_command = Format(scratch, "bash <<EOF\nclang %S -o cfile.exe -g -Wall -Wno-missing-braces -Wno-writable-strings -Wno-writable-strings -fsanitize=address -fdiagnostics-absolute-paths -lm \n./cfile.exe \nrm cfile.exe\nEOF", primary.buffer->name);
}
ExecBuild(windows_command, unix_command, GetDirectory(primary.buffer)); 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");

View File

@@ -12,7 +12,7 @@ void CMD_ShowCommands() {
} }
} }
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() {
@@ -31,7 +31,7 @@ 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() {
@@ -50,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) {
@@ -66,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

@@ -69,10 +69,9 @@ BufferID LoadConfig(String config_path) {
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) {
@@ -96,17 +88,17 @@ void Set(String string) {
string = Skip(string, 1); string = Skip(string, 1);
String quote = SkipUntil(&string, {&c, 1}); String quote = SkipUntil(&string, {&c, 1});
ExpectP(At(string, 0) == c, ":Set %S <error here>, unclosed quote", name); 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); *var->string = Intern(&GlobalInternTable, quote);
} else if (var->type == VariableType_Int) { } else if (var->type == VariableType_Int) {
ExpectP(IsDigit(At(string, 0)), "Expected an integer to follow the command name, instead got: %S", string); ExpectP(IsDigit(At(string, 0)), "Expected an integer to follow the command name, instead got: %S", string);
Int number = SkipInt(&string); Int number = SkipInt(&string);
ReportConsolef(":Set %S %lld", name, number); // ReportConsolef(":Set %S %lld", name, number);
*var->i = number; *var->i = number;
} else if (var->type == VariableType_Float) { } else if (var->type == VariableType_Float) {
ExpectP(IsDigit(At(string, 0)), "Expected float to follow the command name, instead got: %S", string); ExpectP(IsDigit(At(string, 0)), "Expected float to follow the command name, instead got: %S", string);
Float number = SkipFloat(&string); Float number = SkipFloat(&string);
ReportConsolef(":Set %S %f", name, number); // ReportConsolef(":Set %S %f", name, number);
*var->f = number; *var->f = number;
} else if (var->type == VariableType_Color) { } else if (var->type == VariableType_Color) {
ExpectP(IsHexDigit(At(string, 0)), "Expected hex integer to follow the command name, instead got: %S", string); ExpectP(IsHexDigit(At(string, 0)), "Expected hex integer to follow the command name, instead got: %S", string);
@@ -115,7 +107,7 @@ void Set(String string) {
string = Skip(string, 1); string = Skip(string, 1);
begin.len += 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); var->color->value = (uint32_t)strtoll(begin.data, NULL, 16);
} ElseInvalidCodepath(); } ElseInvalidCodepath();
@@ -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;
} }

4
src/plugin_config.h Normal file
View File

@@ -0,0 +1,4 @@
#if PLUGIN_CONFIG
void Set(String string);
String InsertVariables(Allocator allocator, String string);
#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,8 +48,12 @@ void UpdateDebugWindow() {
return; return;
} }
BSet main = GetBSet(PrimaryWindowID); RawReplaceText(set.buffer, GetRange(set.buffer), u"");
RawReplaceText(set.buffer, GetRange(set.buffer), u"Active buffers and views:\n"); #if OS_POSIX
struct rusage usage;
getrusage(RUSAGE_SELF, &usage);
RawAppendf(set.buffer, "MemoryUsage: %lld\n", usage.ru_maxrss);
#endif
For (Views) { For (Views) {
Buffer *buffer = GetBuffer(it->active_buffer); Buffer *buffer = GetBuffer(it->active_buffer);

View File

@@ -8,6 +8,16 @@ void CMD_OpenUpFolder() {
Open(name); Open(name);
} RegisterCommand(CMD_OpenUpFolder, "ctrl-o", "Open current's file directory or up directory in other cases"); } 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) { void InsertDirectoryNavigation(Buffer *buffer) {
Assert(buffer->is_dir); Assert(buffer->is_dir);
Scratch scratch; Scratch scratch;
@@ -61,7 +71,7 @@ void OpenDirectoryNavigation(View *view) {
// view->update_hook = UpdateDirectoryNavigation; // 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

@@ -15,6 +15,7 @@ void New(Window *window, String name = "") {
name = GetUniqueBufferName(dir, "new"); name = GetUniqueBufferName(dir, "new");
} }
WindowOpenBufferView(window, name); WindowOpenBufferView(window, name);
RunGCThisFrame = true;
} }
void CMD_New() { void CMD_New() {
@@ -38,6 +39,7 @@ void CMD_Reopen() {
} RegisterCommand(CMD_Reopen, "", "Reads again from disk the current buffer"); } RegisterCommand(CMD_Reopen, "", "Reads again from disk the current buffer");
void CO_Rename(mco_coro *co) { void CO_Rename(mco_coro *co) {
CCtx *ctx = GetCoroutineContext();
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
Buffer *original_buffer = main.buffer; Buffer *original_buffer = main.buffer;
JumpTempBuffer(&main); JumpTempBuffer(&main);
@@ -60,11 +62,12 @@ void CO_Rename(mco_coro *co) {
return; return;
} }
String16 string16 = GetString(main.buffer, {a.range.max + 1, b.range.max}); String16 string16 = GetString(main.buffer, {a.range.max + 1, b.range.max});
String string = ToString(CoCurr->arena, string16); String string = ToString(ctx->arena, string16);
original_buffer->name = Intern(&GlobalInternTable, string); 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"); } 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) { void CO_Close(mco_coro *co) {
CCtx *ctx = GetCoroutineContext();
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
if (main.buffer->special || main.buffer->temp) { if (main.buffer->special || main.buffer->temp) {
Close(main.view->id); Close(main.view->id);
@@ -92,7 +95,7 @@ void CO_Close(mco_coro *co) {
return; return;
} }
String question = Format(CoCurr->arena, "Do you want to save [%S] before closing?", main.buffer->name); String question = Format(ctx->arena, "Do you want to save [%S] before closing?", main.buffer->name);
UIAction ui_action = QueryUserYesNoCancel(co, main, question); UIAction ui_action = QueryUserYesNoCancel(co, main, question);
if (ui_action == UIAction_Yes) { if (ui_action == UIAction_Yes) {
SaveBuffer(main.buffer); SaveBuffer(main.buffer);
@@ -106,7 +109,6 @@ void CO_Close(mco_coro *co) {
void CMD_DeleteFile() { void CMD_DeleteFile() {
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
String buffer_name = main.buffer->name;
DeleteFile(main.buffer->name); DeleteFile(main.buffer->name);
Close(main.buffer->id); Close(main.buffer->id);
} RegisterCommand(CMD_DeleteFile, "", "Close the open buffer and delete it's corresponding file on disk"); } RegisterCommand(CMD_DeleteFile, "", "Close the open buffer and delete it's corresponding file on disk");
@@ -116,9 +118,10 @@ void CO_CloseAll(mco_coro *co) {
} RegisterCoroutineCommand(CO_CloseAll, "", "Ask user which files to save and close all open normal views and buffers"); } RegisterCoroutineCommand(CO_CloseAll, "", "Ask user which files to save and close all open normal views and buffers");
void CO_ReplaceAll(mco_coro *co) { void CO_ReplaceAll(mco_coro *co) {
CCtx *ctx = GetCoroutineContext();
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
String16 string = FetchLoadWord(main.view); String16 string = FetchLoadWord(main.view);
String string8 = ToString(CoCurr->arena, string); String string8 = ToString(ctx->arena, string);
String16 needle = {}; String16 needle = {};
String16 replace = {}; String16 replace = {};
@@ -138,13 +141,13 @@ void CO_ReplaceAll(mco_coro *co) {
field_seek = BaseFindNext(main.buffer, u"for::", MakeCaret(0), SeekFlag_None); field_seek = BaseFindNext(main.buffer, u"for::", MakeCaret(0), SeekFlag_None);
Range range = {field_seek.range.max, main.buffer->len}; Range range = {field_seek.range.max, main.buffer->len};
needle = GetString(main.buffer, range); needle = Copy16(ctx->arena, GetString(main.buffer, range));
} }
{ {
JumpTempBuffer(&main); JumpTempBuffer(&main);
NextActiveWindowID = main.window->id; NextActiveWindowID = main.window->id;
RawAppendf(main.buffer, ":Submit (enter) :Cancel (escape)\nString to replace with::%S", ToString(CoCurr->arena, needle)); 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); Caret field_seek = BaseFindNext(main.buffer, u"with::", MakeCaret(0), SeekFlag_None);
main.view->carets[0] = MakeCaret(main.buffer->len, field_seek.range.max); main.view->carets[0] = MakeCaret(main.buffer->len, field_seek.range.max);

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

@@ -294,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;

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) {
@@ -10,12 +10,13 @@ void SetProjectDirectory(String dir) {
} }
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;
@@ -45,18 +46,18 @@ 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) { void OpenProject(String directory) {
SetProjectDirectory(directory); SetProjectFolder(directory);
#if PLUGIN_CONFIG #if PLUGIN_CONFIG
Scratch scratch; Scratch scratch;
for (FileIter it = IterateFiles(scratch, directory); IsValid(it); Advance(&it)) { for (FileIter it = IterateFiles(scratch, directory); IsValid(it); Advance(&it)) {
@@ -110,7 +111,7 @@ void CO_CreateProject(mco_coro *co) {
String src_dir = Format(scratch, "%S/src", project_dir); String src_dir = Format(scratch, "%S/src", project_dir);
MakeDir(project_dir); MakeDir(project_dir);
MakeDir(src_dir); MakeDir(src_dir);
SetProjectDirectory(project_dir); SetProjectFolder(project_dir);
Buffer *project = BufferOpenFile(Format(scratch, "%S/project.te", 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); RawAppendf(project, ":OpenCode\n:Set BinaryUnderDebug '%S/build/main.exe'\n", project_dir);

View File

@@ -0,0 +1 @@
void SetProjectFolder(String name);

View File

@@ -64,3 +64,7 @@ void Serialize(Buffer *buffer, Event *e) {
#undef X #undef X
SerializeEnd(buffer); SerializeEnd(buffer);
} }
void CMD_CopyEvents() {
SaveStringInClipboard(GetString(EventBuffer));
} RegisterCommand(CMD_CopyEvents, "ctrl-shift-alt-j", "Copy all the events from the EventBuffer");

View File

@@ -2098,6 +2098,7 @@ uint8_t reply_buf[REPLY_BUF_SIZE];
ClientContext RDBG_Ctx; ClientContext RDBG_Ctx;
bool RDBG_InitConnection(mco_coro *co, bool create_session = true) { bool RDBG_InitConnection(mco_coro *co, bool create_session = true) {
CCtx *ctx = GetCoroutineContext();
ReportConsolef("Remedybg: InitConnection"); 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) {
@@ -2112,15 +2113,18 @@ bool RDBG_InitConnection(mco_coro *co, bool create_session = true) {
if (create_session) { 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) {
Scratch scratch; Scratch scratch;
for (FileIter it = IterateFiles(scratch, ProjectDirectory); IsValid(it); Advance(&it)) { for (FileIter it = IterateFiles(scratch, ProjectFolder); IsValid(it); Advance(&it)) {
if (EndsWith(it.filename, ".rdbg")) { if (EndsWith(it.filename, ".rdbg")) {
file = Intern(&GlobalInternTable, it.absolute_path); file = Intern(&GlobalInternTable, it.absolute_path);
break; break;
@@ -2129,14 +2133,17 @@ bool RDBG_InitConnection(mco_coro *co, bool create_session = true) {
} }
if (file.len == 0) { if (file.len == 0) {
file = QueryUserFile(co); ReportErrorf("Couldn't find neither .rdbg file, nor use the BinaryUnderDebug variable to locate the binary");
return false;
} }
} }
String session_name = Format(CoCurr->arena, "te%llu", GetTimeNanos()); String session_name = Format(ctx->arena, "te%llu", GetTimeNanos());
String remedy_string = Format(CoCurr->arena, "%S --servername %S", RemedyBGPath, session_name); String remedy_string = Format(ctx->arena, "%S --servername %S", RemedyBGPath, session_name);
ReportConsolef("Remedybg: %S", remedy_string); ReportConsolef("Remedybg: %S", remedy_string);
Exec(LogView->id, true, remedy_string, GetPrimaryDirectory()); 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);
@@ -2171,7 +2178,7 @@ bool RDBG_InitConnection(mco_coro *co, bool create_session = true) {
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)) {

View File

@@ -0,0 +1,129 @@
struct SearchOpenBuffersParams {
String16 needle;
BufferID buffer;
};
void Coro_SearchOpenBuffers(mco_coro *co) {
CCtx *ctx = GetCoroutineContext();
SearchOpenBuffersParams *param = (SearchOpenBuffersParams *)ctx->user_ctx;
Array<BufferID> buffers = {ctx->arena};
For (Buffers) {
Add(&buffers, it->id);
}
SeekFlag flag = SearchCaseSensitive ? SeekFlag_None : SeekFlag_IgnoreCase;
if (SearchWordBoundary) {
flag |= SeekFlag_WordBoundary;
}
bool ignore_case = flag & SeekFlag_IgnoreCase ? true : false;
bool word_boundary = flag & SeekFlag_WordBoundary;
Int max_iters_per_yield = 50000;
Int yield_iters = 0;
ForItem (id, buffers) {
Buffer *it = GetBuffer(id, NULL);
if (it == NULL || it->special || it->temp || it->dont_try_to_save_in_bulk_ops) {
continue;
}
#if PLUGIN_DIRECTORY_NAVIGATION
if (it->is_dir) {
continue;
}
#endif
// :Seek
Buffer *out_buffer = GetBuffer(param->buffer);
String16 find = param->needle;
String16 string = GetString(it);
for (int64_t i = 0; i < string.len; i++) {
String16 substring = GetSlice(string, i, i + find.len);
if (AreEqual(substring, find, ignore_case)) {
bool ok_boundary = true;
if (word_boundary) {
String16 left = GetSlice(string, i - 1, i);
String16 right = GetSlice(string, i + find.len, i + find.len + 1);
if (left.len != 0 && !IsNonWord(left.data[0])) {
ok_boundary = false;
}
if (right.len != 0 && !IsNonWord(right.data[0])) {
ok_boundary = false;
}
}
if (ok_boundary) {
Int line = PosToLine(it, i);
Range range = GetLineRangeWithoutNL(it, line);
Int column = i - range.min;
String16 line_string = GetString(it, range);
U8 *pos = ctx->arena.start;
String line_string8 = ToString(ctx->arena, line_string);
RawAppendf(out_buffer, "%S ||> %S:%lld:%lld\n", line_string8, it->name, (long long)line + 1, (long long)column + 1);
Unwind(&ctx->arena, pos);
}
}
yield_iters += 1;
if (yield_iters > max_iters_per_yield) {
Yield(co);
yield_iters = 0;
out_buffer = GetBuffer(param->buffer, NULL);
if (out_buffer == NULL) {
return;
}
it = GetBuffer(id, NULL);
if (it == NULL) {
goto skip_buffer;
}
}
}
skip_buffer:;
}
}
void UpdateSearchOpenBuffersView() {
Scratch scratch;
BSet active = GetBSet(ActiveWindowID);
String16 line_string = GetLineStringWithoutNL(active.buffer, 0);
uint64_t hash = HashBytes(line_string.data, line_string.len * sizeof(char16_t));
if (active.view->prev_search_line_hash != hash) {
active.view->prev_search_line_hash = hash;
if (line_string.len > 0) {
// @todo: somehow reintroduce history but manual, do the UndoKinds EditKind or something
// and implement EditKind_ExactBufferContent just save the
Caret caret = active.view->carets[0];
SelectEntireBuffer(active.view);
Replace(active.view, line_string);
Append(active.view, "\n", false);
RemoveCoroutine("Coro_SearchOpenBuffers");
CCtx *dat = AddCoroutine(Coro_SearchOpenBuffers);
SearchOpenBuffersParams *param = AllocType(dat->arena, SearchOpenBuffersParams);
param->needle = Copy16(dat->arena, line_string);
param->buffer = active.buffer->id;
dat->user_ctx = param;
dat->dont_wait_until_resolved = true;
ResumeCoroutine(dat);
active.view->carets[0] = caret;
}
}
}
void CMD_SearchOpenBuffers() {
BSet main = GetBSet(PrimaryWindowID);
String16 string = {};
if (main.view->carets.len == 1 && GetSize(main.view->carets[0]) > 0) {
string = GetString(main.buffer, main.view->carets[0].range);
}
NextActiveWindowID = main.window->id;
JumpTempBuffer(&main);
main.buffer->no_history = true;
AddCommand(&main.view->commands, "Open", OpenKeySet, CMD_OpenAndSetGotoNext);
main.view->update_hook = UpdateSearchOpenBuffersView;
if (string.len) {
SelectRange(main.view, GetLineRangeWithoutNL(main.buffer, 0));
Replace(main.view, string);
}
SelectRange(main.view, GetLineRangeWithoutNL(main.buffer, 0));
} RegisterCommand(CMD_SearchOpenBuffers, "ctrl-shift-f", "Interactive search over the entire project in a new buffer view");

View File

@@ -1,3 +1,5 @@
Int SearchBufferChangeID;
void CMD_Search() { void CMD_Search() {
BSet main = GetBSet(ActiveWindowID); BSet main = GetBSet(ActiveWindowID);
String16 string = {}; String16 string = {};
@@ -13,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) {
@@ -20,7 +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);
CenterView(PrimaryWindowID); if (!IsMainCaretOnScreen(main.window).caret_on_screen) {
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) {
@@ -89,7 +95,6 @@ void LayoutSearchWindow(Rect2I *rect, int16_t wx, int16_t wy) {
window->line_numbers_rect = CutLeft(&window->document_rect, window->font->char_spacing * 6); window->line_numbers_rect = CutLeft(&window->document_rect, window->font->char_spacing * 6);
} }
Int SearchBufferChangeID;
void UpdateSearchWindow() { void UpdateSearchWindow() {
if (ActiveWindowID == SearchWindowID) { if (ActiveWindowID == SearchWindowID) {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
@@ -98,7 +103,9 @@ void UpdateSearchWindow() {
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);
CenterView(main.window->id); if (!IsMainCaretOnScreen(main.window).caret_on_screen) {
CenterView(main.window->id);
}
SearchBufferChangeID = active.buffer->change_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) {
@@ -70,10 +71,10 @@ void UpdateStatusWindow() {
String commands = Format(scratch, ":Errors[%d]", ErrorCount); String commands = Format(scratch, ":Errors[%d]", ErrorCount);
if (main.buffer->changed_on_disk) { if (main.buffer->changed_on_disk) {
commands = Format(scratch, "%S :Reopen"); commands = Format(scratch, "%S :Reopen", commands);
} }
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); commands = Format(scratch, "%S :KillProcess", commands);
break; break;
} }
@@ -81,7 +82,7 @@ void UpdateStatusWindow() {
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); 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) { For (ActiveProcesses) {
if (it.view_id == main.view->id.id) { 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);
} }
} }

123
src/plugin_tests.cpp Normal file
View File

@@ -0,0 +1,123 @@
#if PLUGIN_TESTS
void Wait(mco_coro *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 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);}
{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);
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

View File

@@ -9,14 +9,14 @@ void CMD_Prev() {
main.window->skip_checkpoint = true; main.window->skip_checkpoint = true;
JumpBack(main.window); JumpBack(main.window);
NextActiveWindowID = main.window->id; 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() { void CMD_Next() {
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
main.window->skip_checkpoint = true; main.window->skip_checkpoint = true;
JumpForward(main.window); JumpForward(main.window);
NextActiveWindowID = main.window->id; 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() { void CMD_FocusLeftWindow() {
NextActiveWindowID = SwitchWindow(DIR_LEFT)->id; NextActiveWindowID = SwitchWindow(DIR_LEFT)->id;
@@ -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");
@@ -60,9 +74,9 @@ void CMD_CloseWindow() {
void CMD_GotoNextInList() { void CMD_GotoNextInList() {
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
GotoNextInList(main.window, 1); 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() { void CMD_GotoPrevInList() {
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
GotoNextInList(main.window, -1); 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");

View File

@@ -1,5 +1,6 @@
// MAAAAAAAAAAAAN I DONT LIKE THIS CODE, BUT HOPE IT WORKS // 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 { struct Lexer2 {
char16_t *at; char16_t *at;
}; };
@@ -55,13 +56,16 @@ void CWSLexIdentifiers(Array<StringAndDistance> *out_idents, Buffer *buffer) {
Array<StringAndDistance> idents = {CWS.arena}; Array<StringAndDistance> idents = {CWS.arena};
String16 string = GetString(buffer); String16 string = GetString(buffer);
Lexer2 lexer = BeginLexing(string.data); Lexer2 lexer = BeginLexing(string.data);
for (int i = 0;; i += 1) { for (;;) {
String16 token = Next(&lexer); String16 token = Next(&lexer);
if (token.len <= 0) { if (token.len <= 0) {
break; break;
} }
if (StartsWith(token, CWS.prefix_string) && token != CWS.prefix_string) { if (StartsWith(token, CWS.prefix_string) && token != CWS.prefix_string) {
Int pos = token.data - buffer->str; 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); Int distance = Absolute(CWS.original_caret_pos - pos);
int64_t value_index = FindValueIndex(&idents, token); int64_t value_index = FindValueIndex(&idents, token);
if (value_index != -1) { if (value_index != -1) {
@@ -83,42 +87,53 @@ void CWSLexIdentifiers(Array<StringAndDistance> *out_idents, Buffer *buffer) {
} }
} }
void WordComplete(mco_coro *co) { #if 0
Array<BufferID> buffers = {CWS.arena}; void CWSGetNextBuffer(mco_coro *co) {
{ // Would be nice to iterate through 64 last active buffers
Add(&buffers, CWS.buffer->id); // - Then all buffers
ForItem (window, Windows) { Add(&CWS.visited_buffers, CWS.buffer->id);
if (window->visible) { mco_result res = mco_push(co, &CWS.buffer->id, sizeof(CWS.buffer->id));
View *view = GetView(window->active_view); Assert(res == MCO_SUCCESS);
Buffer *buffer = GetBuffer(view->active_buffer); mco_yield(co);
if (!Contains(buffers, buffer->id)) {
Add(&buffers, buffer->id);
}
}
Int len = 0;
For (IterateInReverse(&window->goto_history)) {
View *view = GetView(it.view_id);
Buffer *buffer = GetBuffer(view->active_buffer);
if (!Contains(buffers, buffer->id)) {
Add(&buffers, buffer->id);
len += 1;
if (len > 16) continue;
}
}
}
Window *active = GetWindow(ActiveWindowID);
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);
} }
Buffer *curr_buffer = NULL;
Int buffer_i = 0; Int buffer_i = 0;
Array<StringAndDistance> idents = {CWS.arena}; Array<StringAndDistance> idents = {CWS.arena};
Add(&idents, {CWS.prefix_string, 0}); Add(&idents, {CWS.prefix_string, 0});
for (Int i = 1;;) { for (Int i = 1;;) {
if (i >= idents.len) { if (i >= idents.len) {
while (buffer_i < buffers.len) { while (buffer_i < buffers.len) {
curr_buffer = GetBuffer(buffers[buffer_i++]); Buffer *buffer = GetBuffer(buffers[buffer_i++]);
CWSLexIdentifiers(&idents, curr_buffer); CWSLexIdentifiers(&idents, buffer);
if (i < idents.len) { if (i < idents.len) {
break; break;
} }
@@ -127,17 +142,18 @@ void WordComplete(mco_coro *co) {
goto yield; goto yield;
} }
} }
StringAndDistance it = idents[i]; CWS.last_string = Copy16(CWS.arena, idents[i].string);
String16 ident = Copy16(CWS.arena, it.string); SelectRange(CWS.view, EncloseWord(CWS.buffer, CWS.original_caret_pos));
CWS.last_string = ident; Replace(CWS.view, CWS.last_string);
Range range = EncloseWord(curr_buffer, CWS.original_caret_pos);
View *view = GetViewForFixingWhenBufferCommand(curr_buffer);
SelectRange(view, range);
Replace(view, ident);
yield:; yield:;
mco_yield(co); 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 += CWS.direction;
i = Clamp(i, 0ll, idents.len - 1); i = Clamp(i, (Int)0, idents.len);
} }
} }
@@ -149,7 +165,7 @@ void WordComplete(View *view, Int pos) {
return; return;
} }
bool continue_with_previous = CWS.co && CWS.last_string == prefix && CWS.buffer == buffer; bool continue_with_previous = CWS.co && CWS.last_string == prefix && CWS.buffer == buffer && CWS.view == view;
if (!continue_with_previous) { if (!continue_with_previous) {
if (CWS.co) { if (CWS.co) {
mco_result res = mco_destroy(CWS.co); mco_result res = mco_destroy(CWS.co);
@@ -159,12 +175,13 @@ void WordComplete(View *view, Int pos) {
Release(&CWS.arena); Release(&CWS.arena);
MemoryZero(&CWS, sizeof(CWS)); MemoryZero(&CWS, sizeof(CWS));
// CWS.visited_buffers.allocator = CWS.arena;
CWS.buffer = buffer; CWS.buffer = buffer;
CWS.view = view; CWS.view = view;
CWS.original_caret_pos = pos; CWS.original_caret_pos = pos;
CWS.prefix_string = Copy16(CWS.arena, prefix); CWS.prefix_string = Copy16(CWS.arena, prefix);
mco_desc desc = mco_desc_init(WordComplete, NULL); mco_desc desc = mco_desc_init(WordComplete, 0);
mco_result res = mco_create(&CWS.co, &desc); mco_result res = mco_create(&CWS.co, &desc);
Assert(res == MCO_SUCCESS); Assert(res == MCO_SUCCESS);
} }
@@ -205,7 +222,7 @@ void CMD_WordComplete() {
return; return;
} }
WordComplete(active.view, active.view->carets[0].range.min); WordComplete(active.view, active.view->carets[0].range.min);
} RegisterCommand(CMD_WordComplete, "", "Completes the current word"); } RegisterCommand(CMD_WordComplete, "ctrl-space", "Completes the current word");
void CMD_CompletePrevWord() { void CMD_CompletePrevWord() {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
@@ -214,7 +231,7 @@ void CMD_CompletePrevWord() {
return; return;
} }
CompletePrevWord(active.view, active.view->carets[0].range.min); CompletePrevWord(active.view, active.view->carets[0].range.min);
} RegisterCommand(CMD_CompletePrevWord, "", "Completes the current word"); } RegisterCommand(CMD_CompletePrevWord, "ctrl-shift-space", "If already completing a word, iterate to previous word");
void CMD_CompletePrevWordOrDedent() { void CMD_CompletePrevWordOrDedent() {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
@@ -223,7 +240,7 @@ void CMD_CompletePrevWordOrDedent() {
} else { } else {
IndentSelectedLines(active.view, true); IndentSelectedLines(active.view, true);
} }
} RegisterCommand(CMD_CompletePrevWordOrDedent, "shift-tab", "Completes the current word or it indents it, when single caret with no selection it goes for word complete"); } 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() { void CMD_WordCompleteOrIndent() {
BSet active = GetBSet(ActiveWindowID); BSet active = GetBSet(ActiveWindowID);
@@ -232,4 +249,4 @@ void CMD_WordCompleteOrIndent() {
} else { } else {
IndentSelectedLines(active.view); IndentSelectedLines(active.view);
} }
} RegisterCommand(CMD_WordCompleteOrIndent, "tab", "Completes the current word or it indents it, when single caret with no selection it goes for word complete"); } RegisterCommand(CMD_WordCompleteOrIndent, "", "Completes the current word or it indents it, when single caret with no selection it goes for word complete");

545
src/process.cpp Normal file
View File

@@ -0,0 +1,545 @@
#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(ExecArgs args) {
Scratch scratch;
const int PIPE_READ = 0;
const int PIPE_WRITE = 1;
bool error = false;
String working_dir = Copy(scratch, args.cwd);
chdir(working_dir.data);
Process process = {};
process.args = args;
UnixProcess *plat = (UnixProcess *)&process.platform;
Array<char *> args_cmd = SplitCommand(scratch, args.cmd);
Array<char *> env = {scratch};
char *exe = Copy(scratch, args.exe).data;
For (args.env) {
Add(&env, it.data);
}
Add(&env, (char *)"\0");
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;
}
if (args.open_stdin) {
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, exe, &actions, NULL, args_cmd.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->pid = process_pid;
if (args.open_stdin) {
plat->stdin_write = stdin_desc[PIPE_WRITE];
}
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;
pid_t result = waitpid(plat->pid, &status, WNOHANG);
if (result == 0) {
return true;
}
Assert(result != -1);
process->exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
process->is_valid = false;
return false;
}
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, 16 * 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(ExecArgs args) { 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]);
#if OS_WINDOWS
bool is_bad_bad_ms = EndsWith(it, "AppData\\Local\\Microsoft\\WindowsApps") || EndsWith(it, "AppData/Local/Microsoft/WindowsApps");
if (is_bad_bad_ms) {
continue;
}
#endif
if (FileExists(path_it)) {
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(allocator, 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?
// 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
// the appropriate handles. This happens in this case when git grep calls
// 'less' program which errors out and doesn't print anything
// @todo: maybe I should ask someone smarter about this!
void UpdateProcesses() {
IterRemove(ActiveProcesses) {
IterRemovePrepare(ActiveProcesses);
Scratch scratch;
View *view = GetView(it.args.output_view);
String poll = PollStdout(scratch, &it, false);
if (poll.len > 0) {
Append(view, poll, it.args.scroll_to_end);
} else if (!IsValid(&it)) {
ReportConsolef("process %lld exit code = %d", it.id, it.exit_code);
remove_item = true;
}
}
}
ExecArgs ShellArgs(Allocator allocator, ViewID output_view, String cmd, String working_directory) {
ExecArgs args = {};
SetShell(allocator, &args.exe, &args.cmd, cmd);
args.env = ProcessEnviroment;
args.cwd = working_directory;
args.output_view = output_view;
args.poll_process = 1;
return args;
}
Process Exec(ExecArgs args) {
Process process = SpawnProcess(args);
ReportConsolef("process %lld start. is_valid = %d cmd = %S working_dir = %S", process.id, process.is_valid, args.cmd, args.cwd);
if (process.is_valid && args.poll_process) {
Add(&ActiveProcesses, process);
}
return process;
}
struct ExecResult {
Buffer *buffer;
int exit_code;
};
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);
Buffer *scratch_buff = CreateScratchBuffer(allocator, 4096 * 4);
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);) {
Scratch scratch(allocator);
String poll = PollStdout(scratch, &process, true);
if (poll.len) RawAppend(scratch_buff, poll);
}
return {scratch_buff, process.exit_code};
}
void KillProcess(View *view) {
IterRemove(ActiveProcesses) {
IterRemovePrepare(ActiveProcesses);
ViewID view_id = {it.args.output_view};
if (view_id == view->id) {
KillProcess(&it);
remove_item = true;
String string = "process was killed by user\n";
Append(view, string, it.args.scroll_to_end);
// dont break because that will fuck with removal ...
}
}
}
bool ProcessIsActive(ViewID view) {
For (ActiveProcesses) {
if (it.args.output_view == view) {
return true;
}
}
return false;
}

View File

@@ -119,7 +119,7 @@ Font CreateFont(Atlas *atlas, int32_t size, String path) {
} }
stbtt_fontinfo stb_font; 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) { if (!success) {
return result; return result;
} }

View File

@@ -18,22 +18,20 @@ struct VertexList2D {
struct Shader { struct Shader {
GLuint program; // linked program (vertex+fragment) GLuint program; // linked program (vertex+fragment)
GLint uni_invHalf; // uniform location for inv half-screen size GLint uni_invHalf; // uniform location for inv half-screen size
GLint uni_texture; // sampler location GLint uni_texture; // sampler location
}; };
VertexList2D Vertices; VertexList2D Vertices;
int64_t TotalVertexCount; int64_t TotalVertexCount;
unsigned VBO, VAO; unsigned VBO, VAO;
Shader Shader2D; Shader Shader2D;
BlockArena RenderArena; BlockArena RenderArena;
Rect2 CurrentScissor; Rect2 CurrentScissor;
Font PrimaryFont; Font PrimaryFont;
Font SecondaryFont;
// ---------- shaders (ES3 / WebGL2) ----------
static const char *glsl_vshader_es3 = R"==(#version 300 es static const char *glsl_vshader_es3 = R"==(#version 300 es
precision highp float; precision highp float;
@@ -73,15 +71,15 @@ 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);
} }
} }
// ---------- helper: compile/link ----------
static GLuint CompileShaderSrc(GLenum kind, const char *src) { static GLuint CompileShaderSrc(GLenum kind, const char *src) {
GLuint s = glCreateShader(kind); GLuint s = glCreateShader(kind);
glShaderSource(s, 1, &src, NULL); glShaderSource(s, 1, &src, NULL);
@@ -136,7 +134,6 @@ Shader CreateShaderES3(const char *vsrc, const char *fsrc) {
return out; return out;
} }
// ---------- InitRender for ES3 ----------
void InitRender() { void InitRender() {
#if !OS_WASM #if !OS_WASM
glDebugMessageCallback(&GLDebugCallback, NULL); glDebugMessageCallback(&GLDebugCallback, NULL);
@@ -184,7 +181,6 @@ void BeginFrameRender(float wx, float wy) {
CurrentScissor = Rect0Size(wx, wy); CurrentScissor = Rect0Size(wx, wy);
} }
// ---------- EndFrameRender for ES3 ----------
void EndFrameRender(float wx, float wy, Color color) { void EndFrameRender(float wx, float wy, Color color) {
ProfileFunction(); ProfileFunction();
glEnable(GL_BLEND); glEnable(GL_BLEND);
@@ -288,7 +284,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 +292,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 +323,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;
} }
@@ -399,6 +386,9 @@ void ReloadFont(String path, U32 size) {
Scratch scratch; Scratch scratch;
Atlas atlas = CreateAtlas(scratch, {2048, 2048}); Atlas atlas = CreateAtlas(scratch, {2048, 2048});
PrimaryFont = CreateFont(&atlas, (uint32_t)ClampBottom(2u, (U32)size), path); PrimaryFont = CreateFont(&atlas, (uint32_t)ClampBottom(2u, (U32)size), path);
SecondaryFont = CreateFont(&atlas, 12, path); PrimaryFont.texture_id = UploadAtlas(&atlas);
SecondaryFont.texture_id = PrimaryFont.texture_id = UploadAtlas(&atlas); }
void CleanupRender() {
Dealloc(&PrimaryFont.glyphs);
} }

View File

@@ -1,160 +0,0 @@
String TestDir;
BlockArena TestArena;
void AddCtrlPress(SDL_Keycode key) {
Event event = {};
event.key = key;
event.ctrl = 1;
event.xwindow = 1280;
event.ywindow = 720;
Add(&EventPlayback, event);
}
void AddText(String string) {
Event event = {};
event.kind = EVENT_TEXT_INPUT;
event.xwindow = 1280;
event.ywindow = 720;
event.text = Copy(SysAllocator, string).data;
Add(&EventPlayback, event);
}
void Wait(mco_coro *co) {
Add(&EventPlayback, {EVENT_KIND_INVALID});
for (Event *event = CoYield(co); event->kind != EVENT_KIND_INVALID; event = CoYield(co)) {
}
}
void PlayTestOpen(mco_coro *co) {
// 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);
// AddCtrlPress(SDLK_P);
// Add(&EventPlayback, {EVENT_KEY_PRESS, SDLK_UP, 1280, 720});
// AddCtrlPress(SDLK_Q);
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_PERIOD; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_PERIOD; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_F; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_S; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.text = "s"; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_R; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.text = "r"; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_C; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.text = "c"; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_ESCAPE; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_ESCAPE; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_LCTRL; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_Q; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_LCTRL; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_F; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_T; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.text = "t"; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_E; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.text = "e"; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_S; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.text = "s"; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_T; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.text = "t"; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_ESCAPE; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_ESCAPE; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_LCTRL; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_Q; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_LCTRL; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_F; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_T; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.text = "t"; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_E; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.text = "e"; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_S; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_TEXT_INPUT; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.text = "s"; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_ESCAPE; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_ESCAPE; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_LCTRL; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// {Event ev = {};ev.kind = EVENT_KEY_PRESS; ev.key = SDLK_Q; ev.xwindow = 1910; ev.ywindow = 2040; ev.xmouse = 581; ev.ymouse = 952; ev.ctrl = 1; Add(&EventPlayback, ev);}
// Wait(co);
// {
// BSet main = GetActiveMainSet();
// Assert(main.buffer->name == basic_env_cpp);
// Assert(main.view->carets[0].range.min == 0);
// Assert(main.view->carets[0].range.max == 0);
// Int line = PosToLine(main.buffer, main.view->carets[0].range.min);
// Assert(line == 0);
// }
// AddCtrlPress(SDLK_DOWN);
// AddCtrlPress(SDLK_DOWN);
// AddCtrlPress(SDLK_DOWN);
// Wait(co);
// Range range = {};
// {
// BSet main = GetActiveMainSet();
// Assert(main.view->carets[0].range.min > 0);
// Assert(main.view->carets[0].range.max > 0);
// range = main.view->carets[0].range;
// }
// AddCtrlPress(SDLK_P);
// Add(&EventPlayback, {EVENT_KEY_PRESS, SDLK_UP, 1280, 720});
// AddCtrlPress(SDLK_Q);
// Wait(co);
// Int buffer_len = 0;
// {
// BSet main = GetActiveMainSet();
// Assert(main.buffer->name == basic_env_cpp);
// Assert(main.view->carets[0].range.min == range.min);
// Assert(main.view->carets[0].range.max == range.max);
// buffer_len = main.buffer->len;
// }
// AddText(Format(SysAllocator, "%S:20", basic_env_cpp));
// AddCtrlPress(SDLK_Q);
// Wait(co);
// {
// BSet main = GetActiveMainSet();
// Int pos = main.view->carets[0].range.min;
// Int line = PosToLine(main.buffer, pos);
// Assert(line == 19);
// Assert(main.buffer->name == basic_env_cpp);
// Assert(main.buffer->len != buffer_len);
// Assert(main.buffer->dirty);
// }
// AddCtrlPress(SDLK_Z);
// AddCtrlPress(SDLK_PAGEUP);
// Wait(co);
// {
// BSet main = GetActiveMainSet();
// Assert(main.buffer->len == buffer_len);
// Assert(main.view->carets[0].range.min == 0);
// Assert(main.view->carets[0].range.max == 0);
// }
ReportConsolef("%s DONE", __FUNCTION__);
}
void Test(mco_coro *co) {
Wait(co); // First phase starts immediately but stuff is not initialized so Open acts weird
Open(TestDir);
PlayTestOpen(co);
Release(&TestArena);
// Add(&EventPlayback, {EVENT_QUIT});
}
void InitTests() {
WaitForEvents = false;
TestDir = Format(TestArena, "%S/test_env", GetExeDir(TestArena));
CoRemove("Test");
CoData *data = CoAdd(Test);
data->dont_wait_until_resolved = true;
CoResume(data);
}

View File

@@ -1,8 +1,63 @@
#define PLUGIN_PROFILER 1 /*
## Basics
- [ ] Ctrl+Shift+ArrowDown at the end of buffer, doesn't capture characters on last line without new line, same at the top
- [ ] When inserting parenthesis and selection is there, put the parens on both sides?
- [ ] I noticed WordComplete getting busted after using editor for a while and returning cutdown words but not sure how to repro ...
- [ ] Rewrite WordComplete to use CoroutineCreate, maybe try reducing globals to just the coroutine itself
- [ ] WorkComplete, Bounded search, after a while it becomes problematic with many buffers
## Monaco like design for familiarity
- [ ] 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?
## Refactor
- [ ] Make a platform layer and separate SDL stuff out
- [ ] Report a regression in SDL in newer versions that make it so that events are doubled when unpressing the held button
- [ ] Remove -lbacktrace and add my backtrace library thing
- [ ] Refactor build.sh to accept commands and remove build_web.sh
- [ ] GetWindowZOrder to IterateWindowsInZOrder
- [ ] Investigate reworking history API, tagging modification blocks with carets?
- [ ] How to enable framerate to be unlimited and not break scrolling?
- [ ] When 2 views of same buffer are open, the view with caret below the caret which modifies the view - starts moving and getting messed up
- [ ] The lexing / parsing code for config / bindings appears sloppy would be nice to clean it up but I don't have any ideas
- [ ] Redesign `:QueryFile`
## Features
- [ ] KillProcess in console !!! - should also kill all the children ...........
- [ ] BeginLog and EndLog, show all logs in the UI thing at the end
- [ ] Fuzzy search over executed command ouput
- [ ] Command window but for executing commands without special fuzzy stuff?
- [ ] Implement Regex and jumps using regex so as to allow for using ctags
- [ ] Add UndoKinds (SnapshotUndo, DiffUndo), I want to enable history in fuzzy search buffers without a huge memory cost
- [ ] General parser / data description thing, rewrite other parsers using this, rewrite env handling
- [ ] DirNav, update lister when files change on disk
- [ ] Investigate ways to bind "open" commands to keys from config
- [ ] Ability to access and set clipboard as well as affect selection in the open scripts
- [ ] Syntax for executing commands from root of project, or maybe commands should be executed from root of the CurrentDirectory
## Test
- [ ] Test BlockArena correctnsess - random allocations, writes and undos, try to crash
## Low
- [ ] Shell / terminal buffer plugin (keep the shell alive and talk with it)
- [ ] Fix somehow OpenCode hanging the editor on big files
-----------------------------------------------------------------------
## :QueryFile problems
- User doesn't see that he in a special mode
- Coroutine is boundless here and the boundries of the mode are too lossely defined, it makes it strange when you learn that you are still in this mode
- How do we kill all the views/buffers we entered? Do we care?
*/
#define PLUGIN_PROFILER 1
#include "plugin_profiler.h" #include "plugin_profiler.h"
#include "basic/basic.h" #include "basic.h"
#include "basic/basic.cpp" #include "basic.cpp"
#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"
@@ -13,9 +68,9 @@
#endif #endif
#define MINICORO_IMPL #define MINICORO_IMPL
#include "external/minicoro.h" #include "external/minicoro.h"
#include "render/generated_font.cpp" #include "render_generated_font.cpp"
#include "render/font.cpp" #include "render_font.cpp"
#include "render/opengl.cpp" #include "render_opengl.cpp"
#define PLUGIN_CONFIG 1 #define PLUGIN_CONFIG 1
#define PLUGIN_SEARCH_WINDOW 1 #define PLUGIN_SEARCH_WINDOW 1
@@ -37,6 +92,7 @@
#define PLUGIN_REMEDYBG OS_WINDOWS #define PLUGIN_REMEDYBG OS_WINDOWS
#define PLUGIN_FILE_COMMANDS 1 #define PLUGIN_FILE_COMMANDS 1
#define PLUGIN_WORD_COMPLETE 1 #define PLUGIN_WORD_COMPLETE 1
#define PLUGIN_TESTS 1
#include "plugin_directory_navigation.h" #include "plugin_directory_navigation.h"
#include "plugin_search_window.h" #include "plugin_search_window.h"
@@ -45,6 +101,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"
@@ -57,7 +114,6 @@
#include "commands_clipboard.cpp" #include "commands_clipboard.cpp"
#include "scratch.cpp" #include "scratch.cpp"
#include "draw.cpp" #include "draw.cpp"
#include "test/tests.cpp"
#include "plugin_config.cpp" #include "plugin_config.cpp"
#include "plugin_window_management.cpp" #include "plugin_window_management.cpp"
#include "plugin_directory_navigation.cpp" #include "plugin_directory_navigation.cpp"
@@ -76,6 +132,7 @@
#include "plugin_profiler.cpp" #include "plugin_profiler.cpp"
#include "plugin_file_commands.cpp" #include "plugin_file_commands.cpp"
#include "plugin_word_complete.cpp" #include "plugin_word_complete.cpp"
#include "plugin_tests.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), {
@@ -99,16 +156,13 @@ void SetMouseCursor(SDL_SystemCursor id) {
} }
#else #else
void SetMouseCursor(SDL_SystemCursor id) { void SetMouseCursor(SDL_SystemCursor id) {
static SDL_Cursor *SDL_MouseCursor; if (SDL_MouseCursor == NULL || SDL_MouseCursorLastID != id) {
static SDL_SystemCursor last_id;
if (SDL_MouseCursor == NULL || last_id != id) {
if (SDL_MouseCursor != NULL) { if (SDL_MouseCursor != NULL) {
SDL_DestroyCursor(SDL_MouseCursor); SDL_DestroyCursor(SDL_MouseCursor);
} }
SDL_MouseCursor = SDL_CreateSystemCursor(id); SDL_MouseCursor = SDL_CreateSystemCursor(id);
SDL_SetCursor(SDL_MouseCursor); SDL_SetCursor(SDL_MouseCursor);
last_id = id; SDL_MouseCursorLastID = id;
} }
} }
#endif #endif
@@ -161,9 +215,6 @@ void SetMouseCursor(Event event) {
} }
void CMD_QuitWithoutSaving() { void CMD_QuitWithoutSaving() {
#if PLUGIN_REMEDYBG
QuitDebugger();
#endif
AppIsRunning = false; AppIsRunning = false;
} RegisterCommand(CMD_QuitWithoutSaving, "", "Self explanatory"); } RegisterCommand(CMD_QuitWithoutSaving, "", "Self explanatory");
@@ -175,9 +226,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) {
@@ -253,6 +304,27 @@ void OnCommand(Event event) {
Int p = ScreenSpaceToBufferPos(selected.window, selected.view, selected.buffer, mouse); Int p = ScreenSpaceToBufferPos(selected.window, selected.view, selected.buffer, mouse);
Caret &caret = selected.view->carets[0]; Caret &caret = selected.view->carets[0];
caret = SetFrontWithAnchor(caret, DocumentAnchor, p); caret = SetFrontWithAnchor(caret, DocumentAnchor, p);
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)) { if (ResizerSelected.id != -1 && Mouse(LEFT_UP)) {
@@ -282,7 +354,7 @@ void OnCommand(Event event) {
} }
// Set active window on click // Set active window on click
if (MousePress()) { if (MousePress() || event.kind == EVENT_DROP_FILE) {
Vec2I mouse = MouseVec2I(); Vec2I mouse = MouseVec2I();
For(order) { For(order) {
if (!it->visible) { if (!it->visible) {
@@ -291,6 +363,9 @@ void OnCommand(Event event) {
bool mouse_in_document = AreOverlapping(mouse, it->document_rect); bool mouse_in_document = AreOverlapping(mouse, it->document_rect);
if (mouse_in_document) { if (mouse_in_document) {
NextActiveWindowID = ActiveWindowID = it->id; NextActiveWindowID = ActiveWindowID = it->id;
if (event.kind == EVENT_DROP_FILE) {
WindowOpenBufferView(it, event.text);
}
break; break;
} }
} }
@@ -356,8 +431,11 @@ void OnCommand(Event event) {
DocumentSelected = active.window->id; DocumentSelected = active.window->id;
Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse); Int p = ScreenSpaceToBufferPos(active.window, active.view, active.buffer, mouse);
if (event.alt) Insert(&active.view->carets, MakeCaret(p, p), 0); if (event.alt) {
if (!event.alt && !event.shift) active.view->carets.len = 1; 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]; Caret &caret = active.view->carets[0];
if (event.shift) { if (event.shift) {
@@ -369,7 +447,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);
} }
@@ -408,9 +486,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)) {
@@ -430,10 +506,6 @@ void OnCommand(Event event) {
} }
} }
if (event.kind == EVENT_DROP_FILE) {
WindowOpenBufferView(active.window, event.text);
}
if (event.kind == EVENT_TEXT_INPUT) { if (event.kind == EVENT_TEXT_INPUT) {
String16 string16 = ToString16(scratch, event.text); String16 string16 = ToString16(scratch, event.text);
Replace(active.view, string16); Replace(active.view, string16);
@@ -459,8 +531,11 @@ void OnCommand(Event event) {
} }
} }
IF_SLOW_BUILD(AssertRanges(main.view->carets)); #if SLOW_BUILD
IF_SLOW_BUILD(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) {
@@ -492,6 +567,7 @@ void GarbageCollect() {
return; return;
} }
RunGCThisFrame = false; RunGCThisFrame = false;
ReportConsolef("GarbageCollect");
ProfileFunction(); ProfileFunction();
Allocator sys_allocator = GetSystemAllocator(); Allocator sys_allocator = GetSystemAllocator();
@@ -513,8 +589,8 @@ void GarbageCollect() {
continue; continue;
} }
bool ref = ViewIsReferenced(it); Int ref = ViewIsReferenced(it);
if (ref) { if (ref < 25) {
continue; continue;
} }
@@ -579,7 +655,7 @@ void GarbageCollect() {
JumpToLastValidView(it); JumpToLastValidView(it);
} }
} }
} }
} }
void LayoutWindows(int16_t wx, int16_t wy) { void LayoutWindows(int16_t wx, int16_t wy) {
@@ -678,7 +754,7 @@ void Update(Event event) {
} }
UpdateProcesses(); UpdateProcesses();
CoUpdate(&event); UpdateCoroutines(&event);
For (Windows) { For (Windows) {
@@ -785,6 +861,9 @@ void MainLoop() {
FrameEvents.len = 0; FrameEvents.len = 0;
GetEventsForFrame(&FrameEvents); GetEventsForFrame(&FrameEvents);
For (FrameEvents) { For (FrameEvents) {
if (RecordingMacro) {
Add(&MacroPlayback, it);
}
#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);
@@ -845,20 +924,59 @@ void MainLoop() {
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, char **envp)
#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
InitOS(ReportErrorf);
InitScratch();
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
char **env = envp;
if (!env || !env[0]) {
env = environ;
}
if (env && env[0]) {
for (int i = 0; env[i]; i += 1) {
Add(&ProcessEnviroment, Copy(Perm, env[i]));
}
} else {
ReportErrorf("No environment variables found (envp/environ empty)");
}
#endif
if (1) { if (1) {
RunArenaTest(); RunArenaTest();
@@ -870,25 +988,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;
@@ -912,16 +1011,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);
@@ -931,8 +1036,8 @@ int main(int argc, char **argv)
} }
SDL_SetWindowPosition(SDLWindow, xhalf, yhalf); SDL_SetWindowPosition(SDLWindow, xhalf, yhalf);
SDL_GLContext gl_context = SDL_GL_CreateContext(SDLWindow); SDL_WindowGLContext = SDL_GL_CreateContext(SDLWindow);
SDL_GL_MakeCurrent(SDLWindow, gl_context); SDL_GL_MakeCurrent(SDLWindow, SDL_WindowGLContext);
SDL_ShowWindow(SDLWindow); SDL_ShowWindow(SDLWindow);
// Set icon // Set icon
@@ -960,16 +1065,13 @@ 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, "scratch", "")); 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);
if (InitialScratchContent.len) {
RawAppend(null_buffer, InitialScratchContent);
}
Buffer *logs_buffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectDirectory, "logs", "")); Buffer *logs_buffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectFolder, "logs", ""));
logs_buffer->special = true; logs_buffer->special = true;
View *logs_view = CreateView(logs_buffer->id); View *logs_view = CreateView(logs_buffer->id);
logs_view->special = true; logs_view->special = true;
@@ -977,13 +1079,13 @@ int main(int argc, char **argv)
LogView = logs_view; 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
@@ -992,7 +1094,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) {
@@ -1005,12 +1108,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) {
@@ -1024,7 +1128,6 @@ int main(int argc, char **argv)
Open(argv[i]); Open(argv[i]);
} }
if (Testing) InitTests();
#if PLUGIN_LOAD_VCVARS #if PLUGIN_LOAD_VCVARS
LoadVCVars(); LoadVCVars();
#endif #endif
@@ -1057,6 +1160,10 @@ int main(int argc, char **argv)
} }
#endif #endif
#if PLUGIN_REMEDYBG
QuitDebugger();
#endif
CleanupRender();
SDL_DestroyWindow(SDLWindow); SDL_DestroyWindow(SDLWindow);
SDL_Quit(); SDL_Quit();

View File

@@ -45,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
@@ -68,7 +90,12 @@ 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 {
@@ -293,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)

View File

@@ -1,3 +0,0 @@
#if PLUGIN_CONFIG
void Set(String string);
#endif

View File

@@ -1 +0,0 @@
void SetProjectDirectory(String name);

View File

@@ -1,89 +0,0 @@
struct SearchOpenBuffersParams {
String16 needle;
ViewID view;
};
void Coro_SearchOpenBuffers(mco_coro *co) {
SearchOpenBuffersParams *param = (SearchOpenBuffersParams *)CoCurr->user_ctx;
Array<BufferID> buffers = {CoCurr->arena};
For (Buffers) {
Add(&buffers, it->id);
}
ForItem (id, buffers) {
Buffer *it = GetBuffer(id, NULL);
if (it == NULL || it->special || it->temp || it->dont_try_to_save_in_bulk_ops) {
continue;
}
#if PLUGIN_DIRECTORY_NAVIGATION
if (it->is_dir) {
continue;
}
#endif
{
Scratch scratch;
Array<Caret> occurences = FindAll(scratch, it, param->needle);
View *out_view = GetView(param->view);
ForItem (caret, occurences) {
Int pos = caret.range.min;
Int line = PosToLine(it, pos);
Range range = GetLineRangeWithoutNL(it, line);
Int column = pos - range.min;
String16 line_string = GetString(it, range);
String line_string8 = ToString(scratch, line_string);
Appendf(out_view, "%S ||> %S:%lld:%lld\n", line_string8, it->name, (long long)line + 1, (long long)column + 1);
}
}
CoYield(co);
}
}
void UpdateSearchOpenBuffersView() {
Scratch scratch;
BSet active = GetBSet(ActiveWindowID);
String16 line_string = GetLineStringWithoutNL(active.buffer, 0);
uint64_t hash = HashBytes(line_string.data, line_string.len * sizeof(char16_t));
if (active.view->prev_search_line_hash != hash) {
active.view->prev_search_line_hash = hash;
if (line_string.len > 0) {
// @todo: somehow reintroduce history but manual, do the UndoKinds EditKind or something
// and implement EditKind_ExactBufferContent just save the
Caret caret = active.view->carets[0];
SelectEntireBuffer(active.view);
Replace(active.view, line_string);
Append(active.view, "\n", false);
CoRemove("Coro_SearchOpenBuffers");
CoData *dat = CoAdd(Coro_SearchOpenBuffers);
SearchOpenBuffersParams *param = AllocType(dat->arena, SearchOpenBuffersParams);
param->needle = Copy16(dat->arena, line_string);
param->view = active.view->id;
dat->user_ctx = param;
dat->dont_wait_until_resolved = true;
CoResume(dat);
active.view->carets[0] = caret;
}
}
}
void CMD_SearchOpenBuffers() {
BSet main = GetBSet(PrimaryWindowID);
String16 string = {};
if (main.view->carets.len == 1 && GetSize(main.view->carets[0]) > 0) {
string = GetString(main.buffer, main.view->carets[0].range);
}
NextActiveWindowID = main.window->id;
JumpTempBuffer(&main);
main.buffer->no_history = true;
AddCommand(&main.view->commands, "Open", OpenKeySet, CMD_OpenAndSetGotoNext);
main.view->update_hook = UpdateSearchOpenBuffersView;
if (string.len) {
SelectRange(main.view, GetLineRangeWithoutNL(main.buffer, 0));
Replace(main.view, string);
}
SelectRange(main.view, GetLineRangeWithoutNL(main.buffer, 0));
} RegisterCommand(CMD_SearchOpenBuffers, "ctrl-shift-f", "Interactive search over the entire project in a new buffer view");

View File

@@ -1,99 +0,0 @@
// 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
// child process and then it won't print anything because it won't have
// the appropriate handles. This happens in this case when git grep calls
// 'less' program which errors out and doesn't print anything
// @todo: maybe I should ask someone smarter about this!
void UpdateProcesses() {
IterRemove(ActiveProcesses) {
IterRemovePrepare(ActiveProcesses);
Scratch scratch;
View *view = GetView(ViewID{it.view_id});
String poll = PollStdout(scratch, &it, false);
if (poll.len) {
Append(view, poll, it.scroll_to_end);
}
if (!IsValid(&it)) {
ReportConsolef("process %lld exit code = %d", it.id, it.exit_code);
remove_item = true;
}
}
}
void Exec(ViewID view, bool scroll_to_end, String cmd, String working_dir) {
Process process = SpawnProcess(cmd, working_dir, {}, ProcessEnviroment);
ReportConsolef("process %lld start. is_valid = %d cmd = %S working_dir = %S", process.id, process.is_valid, cmd, working_dir);
process.view_id = view.id;
process.scroll_to_end = scroll_to_end;
if (process.is_valid) {
Add(&ActiveProcesses, process);
}
}
void Exec(ViewID view, bool scroll_to_end, String16 cmd16, String working_dir) {
Scratch scratch;
String cmd = ToString(scratch, cmd16);
Exec(view, scroll_to_end, cmd, working_dir);
}
struct ExecResult {
Buffer *buffer;
int exit_code;
};
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);
Buffer *scratch_buff = CreateScratchBuffer(allocator, 4096 * 4);
Process process = SpawnProcess(cmd, working_dir, stdin_string, ProcessEnviroment);
for (;IsValid(&process);) {
Scratch scratch(allocator);
String poll = PollStdout(scratch, &process, true);
if (poll.len) RawAppend(scratch_buff, poll);
}
return {scratch_buff, process.exit_code};
}
void KillProcess(View *view) {
IterRemove(ActiveProcesses) {
IterRemovePrepare(ActiveProcesses);
ViewID view_id = {it.view_id};
if (view_id == view->id) {
KillProcess(&it);
remove_item = true;
String string = "process was killed by user\n";
Append(view, string, it.scroll_to_end);
// dont break because that will fuck with removal ...
}
}
}
bool ProcessIsActive(ViewID view) {
For (ActiveProcesses) {
if (it.view_id == view.id) {
return true;
}
}
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

@@ -5,6 +5,7 @@ void JumpTempBuffer(BSet *set, String buffer_name) {
set->view = WindowOpenBufferView(set->window, buffer_name); set->view = WindowOpenBufferView(set->window, buffer_name);
set->buffer = GetBuffer(set->view->active_buffer); set->buffer = GetBuffer(set->view->active_buffer);
set->buffer->temp = true; set->buffer->temp = true;
RunGCThisFrame = true;
} }
void AddCommand(Array<Command> *arr, String name, Trigger *trigger, CMDFunction *function) { void AddCommand(Array<Command> *arr, String name, Trigger *trigger, CMDFunction *function) {
@@ -35,7 +36,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 +85,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"==(
@@ -118,8 +118,9 @@ String16 QueryUserString(mco_coro *co, String ask) {
// 2. Are pointers and globals correct over time? Or might they get deleted etc. // 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? // 3. Imagine a scenario where the coroutine gets deleted before completion, will the memory leak?
UIAction ShowCloseAllUI(mco_coro *co) { UIAction ShowCloseAllUI(mco_coro *co) {
CCtx *ctx = GetCoroutineContext();
BSet main = GetBSet(PrimaryWindowID); BSet main = GetBSet(PrimaryWindowID);
Array<BufferID> buffers = {CoCurr->arena}; Array<BufferID> buffers = {ctx->arena};
For (Buffers) Add(&buffers, it->id); For (Buffers) Add(&buffers, it->id);
ForItem (id, buffers) { ForItem (id, buffers) {
Buffer *it = GetBuffer(id, NULL); Buffer *it = GetBuffer(id, NULL);
@@ -130,7 +131,7 @@ UIAction ShowCloseAllUI(mco_coro *co) {
continue; continue;
} }
String question = Format(CoCurr->arena, "Do you want to save [%S] before closing?", it->name); String question = Format(ctx->arena, "Do you want to save [%S] before closing?", it->name);
UIAction ui_action = QueryUserYesNoCancel(co, main, question); UIAction ui_action = QueryUserYesNoCancel(co, main, question);
it = GetBuffer(id, NULL); it = GetBuffer(id, NULL);
if (it && ui_action == UIAction_Yes) { if (it && ui_action == UIAction_Yes) {
@@ -167,7 +168,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;
@@ -212,12 +213,138 @@ 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));
} 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);
@@ -239,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);
} }
@@ -249,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
@@ -285,7 +420,47 @@ 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
// the parenthesis and whitespace or alternatively we can look for patterns on every
// character move in the loop... For now let's do backtracking. This doesn't handle all paths
// but not sure if that's even what we want. ALL paths is hard.
{
Int i = path.len - 1;
if (At(path, i) == ')') {
i -= 1;
Int end = i;
while (IsDigit(At(path, i))) {
i -= 1;
}
Int start = i;
String b = {path.data + 1 + start, (end - start)};
if (At(path, i) == '(') {
i -= 1;
path.len = i + 1;
result.line = strtoll(b.data, NULL, 10);
} else if (At(path, i) == ',') {
i -= 1;
end = i;
while (IsDigit(At(path, i))) {
i -= 1;
}
start = i;
String a = {path.data + 1 + start, (end - start)};
if (At(path, i) == '(') {
i -= 1;
path.len = i + 1;
result.line = strtoll(a.data, NULL, 10);
result.col = strtoll(b.data, NULL, 10);
}
}
}
}
if (result.line == 0 && At(p, 0) == ':') {
p = Skip(p, 1); p = Skip(p, 1);
result.line = SkipInt(&p); result.line = SkipInt(&p);
if (At(p, 0) == ':') { if (At(p, 0) == ':') {
@@ -293,14 +468,6 @@ ResolvedOpen ResolveOpen(Allocator alo, Window *window, String path, ResolveOpen
Int b = SkipInt(&p); Int b = SkipInt(&p);
result.col = b; result.col = b;
} }
} else if (At(p, 0) == '(') {
p = Skip(p, 1);
result.line = SkipInt(&p);
if (At(p, 0) == ',') {
p = Skip(p, 1);
Int b = SkipInt(&p);
result.col = b;
}
} }
Buffer *existing_buffer = GetBuffer(path, NULL); Buffer *existing_buffer = GetBuffer(path, NULL);
@@ -319,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;
} }
@@ -361,14 +530,29 @@ 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) {
if (set_active) { ExecArgs args = {};// ShellArgs(scratch, LogView->id, o.path, GetPrimaryDirectory());
NextActiveWindowID = set.window->id; args.env = ProcessEnviroment;
args.poll_process = 1;
args.output_view = LogView->id;
args.cwd = GetPrimaryDirectory();
if (o.exec_in_background == 0) {
if (set_active) {
NextActiveWindowID = set.window->id;
}
JumpTempBuffer(&set);
RawAppend(set.buffer, u"\n");
args.output_view = set.view->id;
} }
JumpTempBuffer(&set);
Exec(set.view->id, false, o.path, GetPrimaryDirectory()); if (o.use_python_shell == 1) {
} else if (o.kind == OpenKind_BackgroundExec) { args.exe = FindPython(scratch);
// this shouldn't change the focus/window/view String temp_file = WriteTempFile(scratch, o.path);
Exec(LogView->id, false, o.path, GetPrimaryDirectory()); 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

@@ -41,13 +41,33 @@ View *GetView(ViewID id, View *default_view = Views[0]) {
return result; return result;
} }
API View *GetView(BufferID buffer_id, View *default_view = Views[0]) { View *GetViewForBuffer(Buffer *buffer, bool *is_active = NULL) {
For (Views) { View *view = NULL;
if (it->active_buffer == buffer_id) { if (is_active) {
return it; *is_active = false;
}
} }
return default_view;
BSet active = GetBSet(ActiveWindowID);
if (active.buffer->id == buffer->id) {
if (is_active) {
*is_active = true;
}
return active.view;
}
For(Views) {
if (it->active_buffer != buffer->id) {
continue;
}
view = it;
break;
}
if (!view) {
view = CreateView(buffer->id);
}
return view;
} }
API View *GetView(String name, View *default_view = Views[0]) { API View *GetView(String name, View *default_view = Views[0]) {
@@ -60,6 +80,15 @@ API View *GetView(String name, View *default_view = Views[0]) {
return default_view; return default_view;
} }
API View *FindView(BufferID buffer_id, View *default_view) {
For (Views) {
if (it->active_buffer == buffer_id) {
return it;
}
}
return default_view;
}
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);
@@ -74,6 +103,22 @@ API bool ViewIsCrumb(ViewID view_id) {
return false; return false;
} }
API Int GetViewReferencedInHistoryRating(ViewID view_id) {
ForItem (window, Windows) {
For (window->goto_redo) if (it.view_id == view_id) return 1;
Int i = 0;
For (window->goto_history) {
i += 1;
if (it.view_id == view_id) {
return i;
}
}
}
return 0;
}
bool ViewIsActive(ViewID id) { bool ViewIsActive(ViewID id) {
For (Windows) { For (Windows) {
if (it->active_view == id) { if (it->active_view == id) {
@@ -83,16 +128,16 @@ bool ViewIsActive(ViewID id) {
return false; return false;
} }
API bool ViewIsReferenced(View *view) { API Int ViewIsReferenced(View *view) {
if (view->special) { if (view->special) {
return true; return 1;
} }
if (ViewIsCrumb(view->id)) { if (ViewIsActive(view->id)) {
return true; return 1;
} }
return ViewIsActive(view->id); return GetViewReferencedInHistoryRating(view->id);
} }
bool BufferIsReferenced(Buffer *buffer) { bool BufferIsReferenced(Buffer *buffer) {
@@ -100,7 +145,7 @@ bool BufferIsReferenced(Buffer *buffer) {
return true; return true;
} }
if (GetView(buffer->id, NULL)) { if (FindView(buffer->id, NULL)) {
return true; return true;
} }
@@ -589,11 +634,19 @@ void IndentSelectedLines(View *view, bool shift = false) {
indent_string.len = IndentSize; 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 {
Int whitespace_len = 0; Int whitespace_len = 0;
for (Int i = 0; i < IndentSize && i < string.len && string.data[i] == u' '; 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"");
} }
@@ -604,13 +657,6 @@ void IndentSelectedLines(View *view, bool shift = false) {
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];
if (!shift) {
xypair.front.x += IndentSize;
xypair.back.x += IndentSize;
} else {
xypair.front.x -= IndentSize;
xypair.back.x -= IndentSize;
}
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);
@@ -653,7 +699,7 @@ Array<Edit> ReplaceEx(Allocator scratch, View *view, String16 string) {
} }
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection); EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
return edits; return edits;
} }
void Replace(View *view, String16 string) { void Replace(View *view, String16 string) {
Scratch scratch; Scratch scratch;

View File

@@ -176,21 +176,23 @@ Window *SwitchWindow(int direction) {
return result; 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; Vec2I mworld = mouse - window->document_rect.min + view->scroll;
double px = (double)mworld.x / (double)window->font->char_spacing; double px = (double)mworld.x / (double)window->font->char_spacing;
double py = (double)mworld.y / (double)window->font->line_spacing; double py = (double)mworld.y / (double)window->font->line_spacing;
XY xy = {(Int)round(px), (Int)floor(py)}; 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; return result;
} }
Int ScreenSpaceToBufferPosErrorOutOfBounds(Window *window, View *view, Buffer *buffer, Vec2I mouse) { Int ScreenSpaceToBufferPosErrorOutOfBounds(Window *window, View *view, Buffer *buffer, Vec2I mouse) {
Vec2I mworld = mouse - window->document_rect.min + view->scroll; XY xy = ScreenSpaceToXY(window, view, mouse);
double px = (double)mworld.x / (double)window->font->char_spacing; Int result = XYToPosErrorOutOfBounds(buffer, xy);
double py = (double)mworld.y / (double)window->font->line_spacing;
XY xy = {(Int)round(px), (Int)floor(py)};
Int result = XYToPosErrorOutOfBounds(buffer, xy);
return result; return result;
} }
@@ -274,35 +276,40 @@ void GotoNextInList(Window *window, Int line_offset = 1) {
Buffer *buffer_goto = GetBuffer(view_goto->active_buffer); Buffer *buffer_goto = GetBuffer(view_goto->active_buffer);
int64_t pos = window->goto_list_pos; int64_t pos = window->goto_list_pos;
Int line = PosToLine(buffer_goto, pos); Int start_line = 0;
if (pos < 0) {
start_line = line_offset > 0 ? 0 : buffer_goto->line_starts.len - 1;
} else {
Int line = PosToLine(buffer_goto, pos);
start_line = line + line_offset;
}
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 = start_line; 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; Range before_jump_range = active_view->carets[0].range;
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;
} }
@@ -402,39 +409,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);
}
} }