mirror of https://github.com/microsoft/edit
A backup of edit's original C code
This commit is contained in:
commit
27b2e77f7a
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Microsoft
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakBeforeBraces: Linux
|
||||
ColumnLimit: 0
|
||||
IncludeCategories:
|
||||
- Regex: '^<.*'
|
||||
- Regex: '^".*'
|
||||
InsertNewlineAtEOF: true
|
||||
LineEnding: LF
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: All
|
||||
PointerAlignment: Left
|
||||
SeparateDefinitionBlocks: Always
|
||||
SpaceAfterTemplateKeyword: false
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
Checks: 'clang-diagnostic-*,clang-analyzer-*'
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.idea
|
||||
.vs
|
||||
*.user
|
||||
bin
|
||||
CMakeSettings.json
|
||||
obj
|
||||
out
|
||||
target
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
cmake_minimum_required(VERSION 3.25)
|
||||
|
||||
# Enable CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
|
||||
cmake_policy(SET CMP0141 NEW)
|
||||
|
||||
project(tui LANGUAGES C)
|
||||
|
||||
add_executable(
|
||||
tui
|
||||
src/arena.c
|
||||
src/buffer.c
|
||||
src/helpers.c
|
||||
src/icu.c
|
||||
src/input.c
|
||||
src/loc.c
|
||||
src/main.c
|
||||
src/os_win32.c
|
||||
src/tui.c
|
||||
src/tui.exe.manifest
|
||||
src/ucd.c
|
||||
src/vt.c
|
||||
)
|
||||
|
||||
set_target_properties(
|
||||
tui
|
||||
PROPERTIES
|
||||
C_STANDARD 23
|
||||
CXX_STANDARD 20
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
# Enable hot reload for Debug builds.
|
||||
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<CONFIG:Debug>,EditAndContinue,ProgramDatabase>")
|
||||
|
||||
# We want to always use the DLL runtime, even for Release builds.
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
|
||||
# MSVC currently doesn't have a switch for C23 and CMake only supports C17 for MSVC.
|
||||
set(CMAKE_C23_STANDARD_COMPILE_OPTION "/std:clatest")
|
||||
|
||||
foreach (lang C CXX)
|
||||
# CMake by default manually specifies a list of libs for linking.
|
||||
# Why? Just use the default imports.
|
||||
#set("CMAKE_${lang}_STANDARD_LIBRARIES" "")
|
||||
|
||||
string(
|
||||
CONCAT
|
||||
"CMAKE_${lang}_FLAGS"
|
||||
# Allow the source code to identify the language version.
|
||||
"/Zc:__STDC__ /Zc:__cplusplus "
|
||||
# Eww, who needs RTTI?
|
||||
"/GR- "
|
||||
# Avoids putting inline functions into .obj files.
|
||||
# Reduces their size sometimes significantly.
|
||||
"/Zc:inline "
|
||||
# > For a new project, it may be best to use /W4 in all compilations.
|
||||
# > This option helps ensure the fewest possible hard-to-find code defects.
|
||||
"/W4 /wd4100 "
|
||||
# MSVC is still living in the 80s. Tell it to wake up.
|
||||
"/utf-8 "
|
||||
# Deterministic compilation? Great!
|
||||
"/experimental:deterministic "
|
||||
# Enables new, neat console output diagnostics.
|
||||
"/diagnostics:caret"
|
||||
)
|
||||
|
||||
string(
|
||||
CONCAT
|
||||
"CMAKE_${lang}_FLAGS_DEBUG"
|
||||
# Produce a PDB file in a format that supports the Edit and Continue feature.
|
||||
"/Zi "
|
||||
)
|
||||
|
||||
foreach (mode RELEASE RELWITHDEBINFO)
|
||||
string(
|
||||
CONCAT
|
||||
"CMAKE_${lang}_FLAGS_${mode}"
|
||||
# Turn optimizations on.
|
||||
"/O2 "
|
||||
# Produce a separate PDB file.
|
||||
"/Zi "
|
||||
# NDEBUG is a standard C/C++ precompiler flag for Release mode.
|
||||
"/DNDEBUG "
|
||||
# This will go hand in hand with the linker instructions below.
|
||||
# Currently disabled until I decide if I want to nuke the CRT imports or not.
|
||||
#"/DNODEFAULTLIB"
|
||||
)
|
||||
endforeach ()
|
||||
endforeach ()
|
||||
|
||||
foreach (mode RELEASE RELWITHDEBINFO)
|
||||
string(
|
||||
CONCAT
|
||||
"CMAKE_EXE_LINKER_FLAGS_${mode}"
|
||||
# Ensure we get a nice monolithic PDB.
|
||||
"/DEBUG:FULL "
|
||||
# /INCREMENTAL:NO and /LTCG are important for small binaries.
|
||||
"/INCREMENTAL:NO /LTCG "
|
||||
# /DEBUG disables /OPT:REF and /OPT:ICF which is bad for the binary size.
|
||||
"/OPT:REF,ICF "
|
||||
# This ensures our binary gets a checksum.
|
||||
"/RELEASE "
|
||||
# Remove the default CRT, vcruntime, etc. imports.
|
||||
# We want tight control over what gets imported so that it is tiny and runs everywhere.
|
||||
"/NODEFAULTLIB "
|
||||
# ucrt provides the C runtime functions (printf, etc.)
|
||||
"ucrt.lib "
|
||||
# Confusingly, vcruntime provides memcpy and memset. God knows why it's not in ucrt.
|
||||
"vcruntime.lib "
|
||||
# msvcrt provides the /GS (Buffer Security Check) support code and __isa_available_init.
|
||||
"msvcrt.lib "
|
||||
# Provides the exports for the subset of Win32 APIs
|
||||
# that are common to all Windows 10 devices, and later.
|
||||
"OneCore.lib"
|
||||
)
|
||||
endforeach ()
|
||||
elseif (WIN32)
|
||||
# The clang-cl distribution from Visual Studio currently doesn't support /Zc:__STDC__.
|
||||
# I couldn't find a different workaround.
|
||||
target_compile_definitions(tui PRIVATE __STDC__)
|
||||
target_compile_options(tui PRIVATE -Wall -Werror -Wno-unused-function)
|
||||
endif ()
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"version": 3,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "windows-base",
|
||||
"description": "Target Windows with the Visual Studio development environment.",
|
||||
"hidden": true,
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/out/build/${presetName}",
|
||||
"installDir": "${sourceDir}/out/install/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "cl.exe",
|
||||
"CMAKE_CXX_COMPILER": "cl.exe"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x64-debug",
|
||||
"displayName": "x64 Debug",
|
||||
"description": "Target Windows (64-bit) with the Visual Studio development environment. (Debug)",
|
||||
"inherits": "windows-base",
|
||||
"architecture": {
|
||||
"value": "x64",
|
||||
"strategy": "external"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x64-release",
|
||||
"displayName": "x64 Release",
|
||||
"description": "Target Windows (64-bit) with the Visual Studio development environment. (RelWithDebInfo)",
|
||||
"inherits": "x64-debug",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "arena.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <threads.h>
|
||||
|
||||
#include "os.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#define ALLOC_CHUNK_SIZE (usize)(64 * 1024)
|
||||
|
||||
Arena* arena_create(usize capacity)
|
||||
{
|
||||
capacity = (capacity + ALLOC_CHUNK_SIZE - 1) & ~(ALLOC_CHUNK_SIZE - 1);
|
||||
|
||||
void* base = os_virtual_reserve(capacity);
|
||||
if (!base) {
|
||||
abort();
|
||||
}
|
||||
|
||||
Arena temp = {
|
||||
.capacity = capacity,
|
||||
.base = base,
|
||||
};
|
||||
Arena* arena = arena_malloc(&temp, Arena);
|
||||
memcpy(arena, &temp, sizeof(Arena));
|
||||
return arena;
|
||||
}
|
||||
|
||||
void arena_reset(Arena* arena)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
memset(arena->base + sizeof(Arena), 0xDD, arena->offset);
|
||||
#endif
|
||||
arena->offset = sizeof(Arena);
|
||||
}
|
||||
|
||||
void arena_destroy(Arena* arena)
|
||||
{
|
||||
os_virtual_release(arena->base);
|
||||
}
|
||||
|
||||
static thread_local Arena* s_scratch[2];
|
||||
|
||||
attribute_noinline static void scratch_init()
|
||||
{
|
||||
for (int i = 0; i < 2; i++) {
|
||||
s_scratch[i] = arena_create(64 * 1024 * 1024);
|
||||
}
|
||||
}
|
||||
|
||||
ScratchArena scratch_beg(Arena* conflict)
|
||||
{
|
||||
if (!s_scratch[0]) {
|
||||
scratch_init();
|
||||
}
|
||||
|
||||
Arena* arena = s_scratch[conflict == s_scratch[0]];
|
||||
return (ScratchArena){arena, arena->offset};
|
||||
}
|
||||
|
||||
void scratch_end(ScratchArena scratch)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
memset(scratch.arena->base + scratch.offset, 0xDD, scratch.arena->offset - scratch.offset);
|
||||
#endif
|
||||
|
||||
scratch.arena->offset = scratch.offset;
|
||||
}
|
||||
|
||||
void* arena_malloc_raw(Arena* arena, usize bytes, usize alignment)
|
||||
{
|
||||
bytes = max(bytes, 1);
|
||||
alignment = max(alignment, 1);
|
||||
|
||||
usize offset_old = arena->offset;
|
||||
usize beg = (offset_old + alignment - 1) & ~(alignment - 1);
|
||||
usize end = beg + bytes;
|
||||
u8* ptr = arena->base + beg;
|
||||
|
||||
arena->offset = end;
|
||||
|
||||
if (end > arena->commit) {
|
||||
usize commit_old = arena->commit;
|
||||
usize commit_new = (end + ALLOC_CHUNK_SIZE - 1) & ~(ALLOC_CHUNK_SIZE - 1);
|
||||
|
||||
if (commit_new > arena->capacity) {
|
||||
abort();
|
||||
}
|
||||
|
||||
os_virtual_commit(arena->base + commit_old, commit_new - commit_old);
|
||||
arena->commit = commit_new;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
end += 128;
|
||||
end = min(end, arena->commit);
|
||||
memset(arena->base + offset_old, 0xCD, end - offset_old);
|
||||
#endif
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void* arena_zalloc_raw(Arena* arena, usize bytes, usize alignment)
|
||||
{
|
||||
void* ptr = arena_malloc_raw(arena, bytes, alignment);
|
||||
memset(ptr, 0, bytes);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void slice_reserve(Arena* arena, Slice* dst, usize cap, usize size, usize alignment)
|
||||
{
|
||||
if (cap <= dst->cap) {
|
||||
return;
|
||||
}
|
||||
|
||||
alignment = max(alignment, 1);
|
||||
|
||||
u8* beg_old = dst->beg;
|
||||
usize cap_old = dst->cap;
|
||||
|
||||
usize cap_new = dst->cap * 2;
|
||||
cap_new = max(cap_new, cap);
|
||||
cap_new = max(cap_new, 128);
|
||||
|
||||
usize bytes_old = cap_old * size;
|
||||
usize bytes_new = cap_new * size;
|
||||
|
||||
dst->cap = cap_new;
|
||||
|
||||
if (beg_old + bytes_old == arena->base + arena->offset) {
|
||||
arena_malloc_raw(arena, bytes_new - bytes_old, 1);
|
||||
} else {
|
||||
dst->beg = arena_malloc_raw(arena, bytes_new, alignment);
|
||||
memcpy(dst->beg, beg_old, bytes_old);
|
||||
}
|
||||
}
|
||||
|
||||
void s8_append(Arena* arena, s8* dst, s8 suffix)
|
||||
{
|
||||
if (suffix.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
slice_reserve(arena, (Slice*)dst, dst->len + suffix.len, 1, 1);
|
||||
memcpy(dst->beg + dst->len, suffix.beg, suffix.len);
|
||||
dst->len += suffix.len;
|
||||
}
|
||||
|
||||
void s8_append_repeat(Arena* arena, s8* dst, c8 ch, usize count)
|
||||
{
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
slice_reserve(arena, (Slice*)dst, dst->len + count, 1, 1);
|
||||
memset(dst->beg + dst->len, ch, count);
|
||||
dst->len += count;
|
||||
}
|
||||
|
||||
void s8_append_repeat_string(Arena* arena, s8* dst, s8 rep, usize count)
|
||||
{
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
usize total_len = rep.len * count;
|
||||
|
||||
slice_reserve(arena, (Slice*)dst, dst->len + total_len, 1, 1);
|
||||
|
||||
u8* dst_beg = dst->beg + dst->len;
|
||||
u8* dst_it = dst_beg;
|
||||
u8* src = rep.beg;
|
||||
usize remaining = total_len;
|
||||
usize len_next = 0;
|
||||
usize len = rep.len;
|
||||
|
||||
dst->len += total_len;
|
||||
|
||||
// This loop efficiently repeats the string `rep` `count` times by doubling its length in each iteration.
|
||||
// I.e. "abc" -> "abcabc" -> "abcabcabcabc" -> "abcabcabcabcabcabcabcabc" -> ...
|
||||
// It uses `len` / `len_next` to ensure that the 1st and 2nd iteration both operate with `len == rep.len`,
|
||||
// since in the 1st iteration we need to copy `rep` into `dst` and in the 2nd iteration we perform the first doubling.
|
||||
for (;;) {
|
||||
memcpy(dst_it, src, len);
|
||||
|
||||
remaining -= len;
|
||||
if (remaining == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dst_it += len;
|
||||
src = dst_beg;
|
||||
|
||||
len_next += len;
|
||||
len_next = min(len_next, remaining);
|
||||
len = len_next;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_decimal(Arena* arena, s8* dst, u64 v, bool neg)
|
||||
{
|
||||
// Mapping 2 digits at a time speeds things up a lot because half the divisions are necessary.
|
||||
// I got this idea from https://github.com/fmtlib/fmt which in turn got it
|
||||
// from the talk "Three Optimization Tips for C++" by Andrei Alexandrescu.
|
||||
static const u8 lut[] =
|
||||
"0001020304050607080910111213141516171819"
|
||||
"2021222324252627282930313233343536373839"
|
||||
"4041424344454647484950515253545556575859"
|
||||
"6061626364656667686970717273747576777879"
|
||||
"8081828384858687888990919293949596979899";
|
||||
|
||||
const u64 log10 = u64_log10(v);
|
||||
u64 digits = log10 + 1;
|
||||
usize new_len = dst->len + digits + neg;
|
||||
|
||||
slice_reserve(arena, (Slice*)dst, new_len, 1, 1);
|
||||
dst->len = new_len;
|
||||
|
||||
u8* end = dst->beg + new_len;
|
||||
u8* p = end;
|
||||
|
||||
while (digits > 1) {
|
||||
const u8* s = &lut[(v % 100) * 2];
|
||||
*--p = s[1];
|
||||
*--p = s[0];
|
||||
v /= 100;
|
||||
digits -= 2;
|
||||
}
|
||||
if (digits & 1) {
|
||||
*--p = (u8)('0' + v);
|
||||
}
|
||||
if (neg) {
|
||||
*p = '-';
|
||||
}
|
||||
}
|
||||
|
||||
void s8_append_i64(Arena* arena, s8* dst, i64 value)
|
||||
{
|
||||
write_decimal(arena, dst, value < 0 ? -value : value, value < 0);
|
||||
}
|
||||
|
||||
void s8_append_u64(Arena* arena, s8* dst, u64 value)
|
||||
{
|
||||
write_decimal(arena, dst, value, false);
|
||||
}
|
||||
|
||||
void s8_replace(Arena* arena, s8* dst, usize beg, usize end, s8 replacement)
|
||||
{
|
||||
usize len_old = dst->len;
|
||||
end = min(end, len_old);
|
||||
beg = min(beg, end);
|
||||
|
||||
usize len_new = dst->len - (end - beg) + replacement.len;
|
||||
slice_reserve(arena, (Slice*)dst, len_new, 1, 1);
|
||||
memmove(dst->beg + beg + replacement.len, dst->beg + end, len_old - end);
|
||||
memcpy(dst->beg + beg, replacement.beg, replacement.len);
|
||||
dst->len = len_new;
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
typedef struct Arena {
|
||||
u8* base;
|
||||
usize capacity;
|
||||
usize commit;
|
||||
usize offset;
|
||||
} Arena;
|
||||
|
||||
Arena* arena_create(usize capacity);
|
||||
void arena_reset(Arena* arena);
|
||||
void arena_destroy(Arena* arena);
|
||||
|
||||
typedef struct ScratchArena {
|
||||
Arena* arena;
|
||||
usize offset;
|
||||
} ScratchArena;
|
||||
|
||||
ScratchArena scratch_beg(Arena* conflict);
|
||||
void scratch_end(ScratchArena scratch);
|
||||
|
||||
void* arena_malloc_raw(Arena* arena, usize bytes, usize alignment);
|
||||
void* arena_zalloc_raw(Arena* arena, usize bytes, usize alignment);
|
||||
|
||||
#define arena_malloc(arena, tp) (tp*)arena_malloc_raw(arena, sizeof(tp), max(_Alignof(tp), 16))
|
||||
#define arena_zalloc(arena, tp) (tp*)arena_zalloc_raw(arena, sizeof(tp), max(_Alignof(tp), 16))
|
||||
#define arena_mallocn(arena, tp, n) (tp*)arena_malloc_raw(arena, n * sizeof(tp), max(_Alignof(tp), 16))
|
||||
#define arena_zallocn(arena, tp, n) (tp*)arena_zalloc_raw(arena, n * sizeof(tp), max(_Alignof(tp), 16))
|
||||
|
||||
typedef struct Slice {
|
||||
void* beg;
|
||||
usize len;
|
||||
usize cap;
|
||||
} Slice;
|
||||
|
||||
void slice_reserve(Arena* arena, Slice* dst, usize cap, usize size, usize alignment);
|
||||
#define slice_push(arena, dst, ...) \
|
||||
do { \
|
||||
__typeof__(__VA_ARGS__) item = __VA_ARGS__; \
|
||||
if ((dst)->len >= (dst)->cap) { \
|
||||
slice_reserve(arena, (Slice*)(dst), (dst)->len + 1, _Alignof(__typeof__(item)), sizeof(item)); \
|
||||
} \
|
||||
(dst)->beg[(dst)->len++] = item; \
|
||||
} while (0)
|
||||
|
||||
#define s8_reserve(arena, dst, cap) slice_reserve(arena, (Slice*)dst, cap, 1, 1)
|
||||
void s8_append(Arena* arena, s8* dst, s8 suffix);
|
||||
void s8_append_repeat(Arena* arena, s8* dst, c8 ch, usize count);
|
||||
void s8_append_repeat_string(Arena* arena, s8* dst, s8 rep, usize count);
|
||||
void s8_append_i64(Arena* arena, s8* dst, i64 value);
|
||||
void s8_append_u64(Arena* arena, s8* dst, u64 value);
|
||||
void s8_replace(Arena* arena, s8* dst, usize beg, usize end, s8 replacement);
|
||||
|
||||
attribute_forceinline void s8_append_literal(Arena* arena, s8* dst, const char* str)
|
||||
{
|
||||
#pragma warning(suppress : 4210)
|
||||
extern size_t strlen(char const*);
|
||||
usize len = strlen(str);
|
||||
s8_append(arena, dst, (s8){(c8*)str, len, len});
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
#define s8_append_auto(arena, dst, value) _Generic((value), \
|
||||
char*: s8_append_literal, \
|
||||
s8: s8_append, \
|
||||
signed char: s8_append_i64, \
|
||||
signed short: s8_append_i64, \
|
||||
signed int: s8_append_i64, \
|
||||
signed long: s8_append_i64, \
|
||||
signed long long: s8_append_i64, \
|
||||
unsigned char: s8_append_u64, \
|
||||
unsigned short: s8_append_u64, \
|
||||
unsigned int: s8_append_u64, \
|
||||
unsigned long: s8_append_u64, \
|
||||
unsigned long long: s8_append_u64 \
|
||||
)(arena, dst, value)
|
||||
// clang-format on
|
||||
|
||||
#define s8_append_fmt(arena, dst, ...) \
|
||||
do { \
|
||||
__VA_ARGS_HELPER(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) \
|
||||
(arena, dst, __VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define __VA_ARGS_HELPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) __VA_ARGS_HELPER_##N
|
||||
#define __VA_ARGS_HELPER_1(arena, dst, a1) \
|
||||
s8_append_auto(arena, dst, a1)
|
||||
#define __VA_ARGS_HELPER_2(arena, dst, a1, a2) \
|
||||
s8_append_auto(arena, dst, a1); \
|
||||
s8_append_auto(arena, dst, a2)
|
||||
#define __VA_ARGS_HELPER_3(arena, dst, a1, a2, a3) \
|
||||
s8_append_auto(arena, dst, a1); \
|
||||
s8_append_auto(arena, dst, a2); \
|
||||
s8_append_auto(arena, dst, a3)
|
||||
#define __VA_ARGS_HELPER_4(arena, dst, a1, a2, a3, a4) \
|
||||
s8_append_auto(arena, dst, a1); \
|
||||
s8_append_auto(arena, dst, a2); \
|
||||
s8_append_auto(arena, dst, a3); \
|
||||
s8_append_auto(arena, dst, a4)
|
||||
#define __VA_ARGS_HELPER_5(arena, dst, a1, a2, a3, a4, a5) \
|
||||
s8_append_auto(arena, dst, a1); \
|
||||
s8_append_auto(arena, dst, a2); \
|
||||
s8_append_auto(arena, dst, a3); \
|
||||
s8_append_auto(arena, dst, a4); \
|
||||
s8_append_auto(arena, dst, a5)
|
||||
#define __VA_ARGS_HELPER_6(arena, dst, a1, a2, a3, a4, a5, a6) \
|
||||
s8_append_auto(arena, dst, a1); \
|
||||
s8_append_auto(arena, dst, a2); \
|
||||
s8_append_auto(arena, dst, a3); \
|
||||
s8_append_auto(arena, dst, a4); \
|
||||
s8_append_auto(arena, dst, a5); \
|
||||
s8_append_auto(arena, dst, a6)
|
||||
#define __VA_ARGS_HELPER_7(arena, dst, a1, a2, a3, a4, a5, a6, a7) \
|
||||
s8_append_auto(arena, dst, a1); \
|
||||
s8_append_auto(arena, dst, a2); \
|
||||
s8_append_auto(arena, dst, a3); \
|
||||
s8_append_auto(arena, dst, a4); \
|
||||
s8_append_auto(arena, dst, a5); \
|
||||
s8_append_auto(arena, dst, a6); \
|
||||
s8_append_auto(arena, dst, a7)
|
||||
#define __VA_ARGS_HELPER_8(arena, dst, a1, a2, a3, a4, a5, a6, a7, a8) \
|
||||
s8_append_auto(arena, dst, a1); \
|
||||
s8_append_auto(arena, dst, a2); \
|
||||
s8_append_auto(arena, dst, a3); \
|
||||
s8_append_auto(arena, dst, a4); \
|
||||
s8_append_auto(arena, dst, a5); \
|
||||
s8_append_auto(arena, dst, a6); \
|
||||
s8_append_auto(arena, dst, a7); \
|
||||
s8_append_auto(arena, dst, a8)
|
||||
#define __VA_ARGS_HELPER_9(arena, dst, a1, a2, a3, a4, a5, a6, a7, a8, a9) \
|
||||
s8_append_auto(arena, dst, a1); \
|
||||
s8_append_auto(arena, dst, a2); \
|
||||
s8_append_auto(arena, dst, a3); \
|
||||
s8_append_auto(arena, dst, a4); \
|
||||
s8_append_auto(arena, dst, a5); \
|
||||
s8_append_auto(arena, dst, a6); \
|
||||
s8_append_auto(arena, dst, a7); \
|
||||
s8_append_auto(arena, dst, a8); \
|
||||
s8_append_auto(arena, dst, a9)
|
||||
#define __VA_ARGS_HELPER_10(arena, dst, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \
|
||||
s8_append_auto(arena, dst, a1); \
|
||||
s8_append_auto(arena, dst, a2); \
|
||||
s8_append_auto(arena, dst, a3); \
|
||||
s8_append_auto(arena, dst, a4); \
|
||||
s8_append_auto(arena, dst, a5); \
|
||||
s8_append_auto(arena, dst, a6); \
|
||||
s8_append_auto(arena, dst, a7); \
|
||||
s8_append_auto(arena, dst, a8); \
|
||||
s8_append_auto(arena, dst, a9); \
|
||||
s8_append_auto(arena, dst, a10)
|
||||
|
|
@ -0,0 +1,782 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
#include "os.h"
|
||||
#include "ucd.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define TEXT_BUFFER_ALLOC_MAX_BYTES 0x80000000 // 2GiB
|
||||
#define TEXT_BUFFER_ALLOC_CHUNK_BYTES 0x10000 // 64KiB
|
||||
#define TEXT_BUFFER_GAP_CHUNK_BYTES 0x1000 // 4KiB
|
||||
|
||||
static void* text_buffer_allocator_default_realloc(void* base, usize old_len, usize* new_len)
|
||||
{
|
||||
if (!base) {
|
||||
base = os_virtual_reserve(TEXT_BUFFER_ALLOC_MAX_BYTES);
|
||||
}
|
||||
|
||||
usize len = *new_len;
|
||||
len = (len + TEXT_BUFFER_ALLOC_CHUNK_BYTES - 1) & ~(TEXT_BUFFER_ALLOC_CHUNK_BYTES - 1);
|
||||
|
||||
if (len > TEXT_BUFFER_ALLOC_MAX_BYTES) {
|
||||
abort();
|
||||
}
|
||||
|
||||
os_virtual_commit((u8*)base + old_len, len - old_len);
|
||||
*new_len = len;
|
||||
return base;
|
||||
}
|
||||
|
||||
static void text_buffer_allocator_default_free(void* base, usize len)
|
||||
{
|
||||
if (base) {
|
||||
os_virtual_release(base);
|
||||
}
|
||||
}
|
||||
|
||||
TextBufferAllocator text_buffer_allocator_default()
|
||||
{
|
||||
return (TextBufferAllocator){
|
||||
.realloc = text_buffer_allocator_default_realloc,
|
||||
.free = text_buffer_allocator_default_free,
|
||||
};
|
||||
}
|
||||
|
||||
TextBuffer* text_buffer_create(TextBufferAllocator allocator)
|
||||
{
|
||||
assert(allocator.realloc);
|
||||
assert(allocator.free);
|
||||
|
||||
usize commit = sizeof(TextBuffer);
|
||||
TextBuffer* tb = allocator.realloc(NULL, 0, &commit);
|
||||
if (!tb) {
|
||||
abort();
|
||||
}
|
||||
|
||||
*tb = (TextBuffer){
|
||||
.allocator = allocator,
|
||||
|
||||
.main_base = (c8*)tb,
|
||||
.main_commit = commit,
|
||||
|
||||
.text = (c8*)(tb + 1),
|
||||
|
||||
.word_wrap_columns = -1,
|
||||
};
|
||||
return tb;
|
||||
}
|
||||
|
||||
void text_buffer_destroy(TextBuffer* tb)
|
||||
{
|
||||
tb->allocator.free(tb->undo_base, tb->undo_commit);
|
||||
tb->allocator.free(tb->main_base, tb->main_commit);
|
||||
}
|
||||
|
||||
static void text_buffer_allocate_gap_impl(TextBuffer* tb, usize off, usize len)
|
||||
{
|
||||
c8* data = tb->text;
|
||||
const usize length = tb->text_length;
|
||||
const usize gap_off = tb->gap_off;
|
||||
const usize gap_len = tb->gap_len;
|
||||
|
||||
off = min(off, length);
|
||||
|
||||
// Move the existing gap if it exists
|
||||
if (off != gap_off) {
|
||||
if (gap_len > 0) {
|
||||
//
|
||||
// v gap_off
|
||||
// left: |ABCDEFGHIJKLMN OPQRSTUVWXYZ|
|
||||
// |ABCDEFGHI JKLMNOPQRSTUVWXYZ|
|
||||
// ^ off
|
||||
// move: JKLMN
|
||||
//
|
||||
// v gap_off
|
||||
// !left: |ABCDEFGHIJKLMN OPQRSTUVWXYZ|
|
||||
// |ABCDEFGHIJKLMNOPQRS TUVWXYZ|
|
||||
// ^ off
|
||||
// move: OPQRS
|
||||
//
|
||||
const bool left = off < gap_off;
|
||||
const usize move_src = left ? off : gap_off + gap_len;
|
||||
const usize move_dst = left ? off + gap_len : gap_off;
|
||||
const usize move_len = left ? gap_off - off : off - gap_off;
|
||||
memmove(data + move_dst, data + move_src, move_len);
|
||||
#ifndef NDEBUG
|
||||
memset(data + off, 0xCD, gap_len);
|
||||
#endif
|
||||
}
|
||||
|
||||
tb->gap_off = off;
|
||||
}
|
||||
|
||||
// Enlarge the gap if needed
|
||||
if (len > gap_len) {
|
||||
usize gap_chunk_pad_chars = TEXT_BUFFER_GAP_CHUNK_BYTES / 2;
|
||||
usize gap_len_new = (len + gap_chunk_pad_chars + TEXT_BUFFER_GAP_CHUNK_BYTES - 1) & ~(TEXT_BUFFER_GAP_CHUNK_BYTES - 1);
|
||||
usize bytes_old = tb->main_commit;
|
||||
// The TextBuffer struct is stored in front of the .text data. As such, we need to account for it.
|
||||
usize bytes_new = sizeof(TextBuffer) + length + gap_len_new;
|
||||
|
||||
if (bytes_new > bytes_old) {
|
||||
tb->main_base = tb->allocator.realloc(tb->main_base, bytes_old, &bytes_new);
|
||||
tb->main_commit = bytes_new;
|
||||
}
|
||||
|
||||
c8* gap_beg = data + off;
|
||||
memmove(gap_beg + gap_len_new, gap_beg + gap_len, length - off);
|
||||
#ifndef NDEBUG
|
||||
memset(gap_beg + gap_len, 0xCD, gap_len_new - gap_len);
|
||||
#endif
|
||||
|
||||
tb->gap_len = gap_len_new;
|
||||
}
|
||||
}
|
||||
|
||||
static c8* text_buffer_allocate_gap(TextBuffer* tb, const usize off, const usize len)
|
||||
{
|
||||
if (off != tb->gap_off || len > tb->gap_len) {
|
||||
text_buffer_allocate_gap_impl(tb, off, len);
|
||||
}
|
||||
return tb->text + off;
|
||||
}
|
||||
|
||||
static void text_buffer_commit_gap(TextBuffer* tb, usize len)
|
||||
{
|
||||
len = min(len, tb->gap_len);
|
||||
tb->gap_off += len;
|
||||
tb->gap_len -= len;
|
||||
tb->text_length += len;
|
||||
}
|
||||
|
||||
s8 text_buffer_read_backward(const TextBuffer* tb, usize off)
|
||||
{
|
||||
usize beg;
|
||||
usize len;
|
||||
|
||||
if (off <= tb->gap_off) {
|
||||
// Cursor is before the gap: We can read until the beginning of the buffer.
|
||||
beg = 0;
|
||||
len = off;
|
||||
} else {
|
||||
// Cursor is after the gap: We can read until the end of the gap.
|
||||
beg = tb->gap_off + tb->gap_len;
|
||||
// The cursor_off doesn't account of the gap_len.
|
||||
// (This allows us to move the gap without recalculating the cursor position.)
|
||||
len = off - tb->gap_off;
|
||||
}
|
||||
|
||||
return (s8){tb->text + beg, len, len};
|
||||
}
|
||||
|
||||
s8 text_buffer_read_forward(const TextBuffer* tb, usize off)
|
||||
{
|
||||
usize beg;
|
||||
usize len;
|
||||
|
||||
if (off < tb->gap_off) {
|
||||
// Cursor is before the gap: We can read until the start of the gap.
|
||||
beg = off;
|
||||
len = tb->gap_off - off;
|
||||
} else {
|
||||
// Cursor is after the gap: We can read until the end of the buffer.
|
||||
beg = off + tb->gap_len;
|
||||
len = tb->text_length - off;
|
||||
}
|
||||
|
||||
return (s8){tb->text + beg, len, len};
|
||||
}
|
||||
|
||||
static void text_buffer_record_push_change(TextBuffer* tb, usize beg, usize end, s8 replacement)
|
||||
{
|
||||
if (beg > end || end > tb->text_length) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
usize removed_len = end - beg;
|
||||
usize space_needed = sizeof(TextBufferChange) + removed_len + replacement.len;
|
||||
usize usage_now = tb->undo_usage;
|
||||
usize usage_next = usage_now + space_needed;
|
||||
|
||||
if (usage_next > tb->undo_commit) {
|
||||
tb->undo_base = tb->allocator.realloc(tb->undo_base, tb->undo_commit, &usage_next);
|
||||
tb->undo_commit = usage_next;
|
||||
if (tb->undo_tail) {
|
||||
// This ensures that text_buffer_redo() can differentiate between
|
||||
// an empty undo stack and one that has simply been fully undone.
|
||||
memset(tb->undo_base, 0, sizeof(TextBufferChange));
|
||||
}
|
||||
}
|
||||
|
||||
TextBufferChange* change = (TextBufferChange*)((u8*)tb->undo_base + tb->undo_usage);
|
||||
s8 removed = {(c8*)(change + 1), removed_len, removed_len};
|
||||
s8 inserted = {removed.beg + removed.len, replacement.len, replacement.len};
|
||||
|
||||
removed.len = text_buffer_extract(tb, beg, end, removed.beg);
|
||||
memcpy(inserted.beg, replacement.beg, replacement.len);
|
||||
|
||||
if (tb->undo_tail) {
|
||||
tb->undo_tail->next = change;
|
||||
}
|
||||
|
||||
change->prev = tb->undo_tail;
|
||||
change->next = NULL;
|
||||
change->cursor = tb->cursor;
|
||||
change->removed = removed;
|
||||
change->inserted = inserted;
|
||||
tb->undo_tail = change;
|
||||
tb->undo_usage = usage_next;
|
||||
tb->dirty = true;
|
||||
}
|
||||
|
||||
enum CodePage_t {
|
||||
CP_UTF7 = 65000,
|
||||
CP_UTF8 = 65001,
|
||||
CP_UTF16LE = 1200,
|
||||
CP_UTF16BE = 1201,
|
||||
CP_UTF32LE = 12000,
|
||||
CP_UTF32BE = 12001,
|
||||
CP_GB18030 = 54936,
|
||||
};
|
||||
|
||||
static int detect_bom(const c8* buffer, int read, u32* cp_out)
|
||||
{
|
||||
u32 cp = 0;
|
||||
int len = 0;
|
||||
|
||||
if (read >= 4) {
|
||||
len = 4;
|
||||
if (memcmp(&buffer[0], "\xFF\xFE\x00\x00", 4) == 0) {
|
||||
cp = CP_UTF32LE;
|
||||
} else if (memcmp(&buffer[0], "\x00\x00\xFE\xFF", 4) == 0) {
|
||||
cp = CP_UTF32BE;
|
||||
} else if (memcmp(&buffer[0], "\x84\x31\x95\x33", 4) == 0) {
|
||||
cp = CP_GB18030;
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (cp == 0 && read >= 3) {
|
||||
len = 3;
|
||||
if (memcmp(&buffer[0], "\xEF\xBB\xBF", 3) == 0) {
|
||||
cp = CP_UTF8;
|
||||
}
|
||||
if (memcmp(&buffer[0], "\x2B\x2F\x76", 3) == 0) {
|
||||
cp = CP_UTF7;
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (cp == 0 && read >= 2) {
|
||||
len = 2;
|
||||
if (memcmp(&buffer[0], "\xFF\xFE", 2) == 0) {
|
||||
cp = CP_UTF16LE;
|
||||
}
|
||||
if (memcmp(&buffer[0], "\xFE\xFF", 2) == 0) {
|
||||
cp = CP_UTF16BE;
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (cp == 0) {
|
||||
cp = CP_UTF8;
|
||||
}
|
||||
|
||||
*cp_out = cp;
|
||||
return len;
|
||||
}
|
||||
|
||||
void text_buffer_read_file(TextBuffer* tb, s8 path)
|
||||
{
|
||||
void* file = os_open_file_for_reading(path);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
usize chunk_size = os_file_size(file) + TEXT_BUFFER_ALLOC_CHUNK_BYTES / 2;
|
||||
|
||||
for (;;) {
|
||||
c8* beg = text_buffer_allocate_gap(tb, tb->text_length, chunk_size);
|
||||
usize len = os_read_file(file, beg, chunk_size);
|
||||
text_buffer_commit_gap(tb, len);
|
||||
if (len < chunk_size) {
|
||||
break;
|
||||
}
|
||||
chunk_size = TEXT_BUFFER_ALLOC_CHUNK_BYTES;
|
||||
}
|
||||
|
||||
os_close_file(file);
|
||||
|
||||
s8 chunk = text_buffer_read_forward(tb, tb->cursor.offset);
|
||||
ucd_newlines_forward(chunk, 0, &tb->stats.lines, COORD_TYPE_MAX);
|
||||
tb->stats.lines += 1;
|
||||
}
|
||||
|
||||
void text_buffer_write_file(TextBuffer* tb, s8 path)
|
||||
{
|
||||
c8* before_gap = tb->text;
|
||||
usize before_gap_len = tb->gap_off;
|
||||
c8* after_gap = before_gap + tb->gap_off + tb->gap_len;
|
||||
usize after_gap_len = tb->text_length - tb->gap_off;
|
||||
|
||||
// TODO: Write to a temp file and do an atomic rename.
|
||||
void* file = os_open_file_for_writing(path);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
os_write_file(file, before_gap, before_gap_len);
|
||||
os_write_file(file, after_gap, after_gap_len);
|
||||
os_close_file(file);
|
||||
|
||||
tb->dirty = false;
|
||||
}
|
||||
|
||||
void text_buffer_selection_update(TextBuffer* tb, Point pos)
|
||||
{
|
||||
if (tb->selection.state == TEXT_BUFFER_SELECTION_NONE || tb->selection.state == TEXT_BUFFER_SELECTION_DONE) {
|
||||
tb->selection.state = TEXT_BUFFER_SELECTION_MAYBE;
|
||||
tb->selection.beg = pos;
|
||||
} else {
|
||||
tb->selection.state = TEXT_BUFFER_SELECTION_ACTIVE;
|
||||
tb->selection.end = pos;
|
||||
}
|
||||
}
|
||||
|
||||
bool text_buffer_selection_end(TextBuffer* tb)
|
||||
{
|
||||
bool active = tb->selection.state == TEXT_BUFFER_SELECTION_ACTIVE;
|
||||
tb->selection.state = active ? TEXT_BUFFER_SELECTION_DONE : TEXT_BUFFER_SELECTION_NONE;
|
||||
return active;
|
||||
}
|
||||
|
||||
static void text_buffer_goto_line_start(TextBuffer* tb, CoordType y)
|
||||
{
|
||||
const usize start_offset = tb->cursor.offset;
|
||||
|
||||
if (y > tb->cursor.logical_pos.y) {
|
||||
while (y > tb->cursor.logical_pos.y) {
|
||||
s8 chunk = text_buffer_read_forward(tb, tb->cursor.offset);
|
||||
if (chunk.len == 0) {
|
||||
break;
|
||||
}
|
||||
tb->cursor.offset += ucd_newlines_forward(chunk, 0, &tb->cursor.logical_pos.y, y);
|
||||
}
|
||||
|
||||
if (tb->cursor.offset != tb->text_length) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
// If there's no trailing newline on the last line in the file, we must go
|
||||
// back to the start of the line, so that `tb->cursor.logical_pos.x == 0` holds true.
|
||||
// TODO: Must debug this to make sure it works.
|
||||
//__debugbreak();
|
||||
y = tb->cursor.logical_pos.y - 1;
|
||||
}
|
||||
|
||||
do {
|
||||
s8 chunk = text_buffer_read_backward(tb, tb->cursor.offset);
|
||||
if (chunk.len == 0) {
|
||||
break;
|
||||
}
|
||||
tb->cursor.offset -= chunk.len - ucd_newlines_backward(chunk, chunk.len, &tb->cursor.logical_pos.y, y);
|
||||
} while (y < tb->cursor.logical_pos.y);
|
||||
|
||||
done:
|
||||
tb->cursor.logical_pos.x = 0;
|
||||
|
||||
if (tb->word_wrap_columns < 0) {
|
||||
// Without line wrapping it's easy: The visual line number equals the logical one.
|
||||
tb->cursor.visual_pos.x = 0;
|
||||
tb->cursor.visual_pos.y = tb->cursor.logical_pos.y;
|
||||
} else {
|
||||
// With line wrapping we need to count how many wrapped lines we've crossed.
|
||||
Point pos = {};
|
||||
usize offset, goal_offset;
|
||||
|
||||
if (start_offset < tb->cursor.offset) {
|
||||
// Cursor moved down. The start position is the previous cursor position,
|
||||
// and so it may not be at the start of a line.
|
||||
pos.x = tb->cursor.visual_pos.x;
|
||||
offset = start_offset;
|
||||
goal_offset = tb->cursor.offset;
|
||||
} else {
|
||||
// Cursor moved up. The start position is the current offset which,
|
||||
// after the code above, is at the start of a line.
|
||||
offset = tb->cursor.offset;
|
||||
goal_offset = start_offset;
|
||||
}
|
||||
|
||||
CoordType delta = 0;
|
||||
|
||||
if (offset < goal_offset) {
|
||||
while (true) {
|
||||
s8 chunk = text_buffer_read_forward(tb, offset);
|
||||
if (chunk.len == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
UcdMeasurement wrap;
|
||||
ucd_measure_forward(chunk, 0, pos, tb->word_wrap_columns, -1, &wrap);
|
||||
offset += wrap.offset;
|
||||
if (offset >= goal_offset) {
|
||||
break;
|
||||
}
|
||||
|
||||
delta += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (start_offset > tb->cursor.offset) {
|
||||
// Cursor moved up.
|
||||
delta = -delta;
|
||||
}
|
||||
|
||||
tb->cursor.visual_pos.x = 0;
|
||||
tb->cursor.visual_pos.y += delta;
|
||||
}
|
||||
}
|
||||
|
||||
void text_buffer_reflow(TextBuffer* tb, CoordType width)
|
||||
{
|
||||
if (width <= 0) {
|
||||
width = -1;
|
||||
}
|
||||
|
||||
if (tb->word_wrap_columns == width) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Point pos = tb->cursor.logical_pos;
|
||||
|
||||
tb->word_wrap_columns = width;
|
||||
tb->cursor = (TextBufferCursor){};
|
||||
|
||||
text_buffer_cursor_move_to_logical(tb, pos);
|
||||
}
|
||||
|
||||
usize text_buffer_cursor_move_to_logical(TextBuffer* tb, Point pos)
|
||||
{
|
||||
CoordType x = max(pos.x, 0);
|
||||
CoordType y = max(pos.y, 0);
|
||||
|
||||
text_buffer_goto_line_start(tb, y);
|
||||
|
||||
if (tb->word_wrap_columns < 0) {
|
||||
while (x > tb->cursor.logical_pos.x) {
|
||||
s8 chunk = text_buffer_read_forward(tb, tb->cursor.offset);
|
||||
if (chunk.len == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
UcdMeasurement m = ucd_measure_forward(chunk, 0, tb->cursor.visual_pos, -1, x - tb->cursor.logical_pos.x, NULL);
|
||||
tb->cursor.offset += m.offset;
|
||||
tb->cursor.logical_pos.x += m.movements;
|
||||
tb->cursor.visual_pos = m.pos;
|
||||
if (m.offset < chunk.len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (x > tb->cursor.logical_pos.x) {
|
||||
// The primary loop exit condition is down below `if (tb->cursor.logical_pos.x >= x)`.
|
||||
while (true) {
|
||||
s8 chunk = text_buffer_read_forward(tb, tb->cursor.offset);
|
||||
if (chunk.len == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
UcdMeasurement wrap;
|
||||
UcdMeasurement m = ucd_measure_forward(chunk, 0, tb->cursor.visual_pos, tb->word_wrap_columns, x - tb->cursor.logical_pos.x, &wrap);
|
||||
tb->cursor.offset += wrap.offset;
|
||||
tb->cursor.logical_pos.x += wrap.movements;
|
||||
tb->cursor.visual_pos = wrap.pos;
|
||||
if (m.offset < chunk.len) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Line wrap.
|
||||
tb->cursor.visual_pos.x = 0;
|
||||
tb->cursor.visual_pos.y += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(tb->cursor.offset <= tb->text_length);
|
||||
assert(tb->cursor.logical_pos.x >= 0);
|
||||
assert(tb->cursor.logical_pos.y >= 0);
|
||||
assert(tb->cursor.logical_pos.y < tb->stats.lines);
|
||||
return tb->cursor.offset;
|
||||
}
|
||||
|
||||
usize text_buffer_cursor_move_to_visual(TextBuffer* tb, Point pos)
|
||||
{
|
||||
CoordType x = max(pos.x, 0);
|
||||
CoordType y = max(pos.y, 0);
|
||||
|
||||
if (tb->word_wrap_columns < 0) {
|
||||
text_buffer_cursor_move_to_logical(tb, (Point){0, y});
|
||||
|
||||
while (x > tb->cursor.visual_pos.x) {
|
||||
s8 chunk = text_buffer_read_forward(tb, tb->cursor.offset);
|
||||
if (chunk.len == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
UcdMeasurement m = ucd_measure_forward(chunk, 0, tb->cursor.visual_pos, x, -1, NULL);
|
||||
tb->cursor.offset += m.offset;
|
||||
tb->cursor.logical_pos.x += m.movements;
|
||||
tb->cursor.visual_pos = m.pos;
|
||||
if (m.offset < chunk.len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (y < tb->cursor.visual_pos.y) {
|
||||
text_buffer_cursor_move_to_logical(tb, (Point){0, tb->cursor.logical_pos.y - 1});
|
||||
}
|
||||
|
||||
if (y > tb->cursor.visual_pos.y || x > tb->cursor.visual_pos.x) {
|
||||
// The primary loop exit condition is down below `if (x <= tb->cursor.logical_pos.x)`.
|
||||
while (true) {
|
||||
s8 chunk = text_buffer_read_forward(tb, tb->cursor.offset);
|
||||
if (chunk.len == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
CoordType column_stop = tb->word_wrap_columns;
|
||||
if (tb->cursor.visual_pos.y >= y) {
|
||||
// We shouldn't have sought beyond the target line.
|
||||
assert(tb->cursor.visual_pos.y == y);
|
||||
column_stop = x;
|
||||
}
|
||||
|
||||
UcdMeasurement wrap;
|
||||
UcdMeasurement m = ucd_measure_forward(chunk, 0, tb->cursor.visual_pos, column_stop, -1, &wrap);
|
||||
tb->cursor.offset += wrap.offset;
|
||||
tb->cursor.logical_pos.x += wrap.movements;
|
||||
tb->cursor.visual_pos = wrap.pos;
|
||||
if (m.offset < chunk.len && tb->cursor.visual_pos.y >= y) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Line wrap.
|
||||
tb->cursor.visual_pos.x = 0;
|
||||
tb->cursor.visual_pos.y += 1;
|
||||
if (m.newline) {
|
||||
tb->cursor.logical_pos.x = 0;
|
||||
tb->cursor.offset += ucd_newlines_forward(chunk, m.offset, &tb->cursor.logical_pos.y, tb->cursor.logical_pos.y + 1) - m.offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(tb->cursor.offset <= tb->text_length);
|
||||
assert(tb->cursor.logical_pos.x >= 0);
|
||||
assert(tb->cursor.logical_pos.y >= 0);
|
||||
assert(tb->cursor.logical_pos.y < tb->stats.lines);
|
||||
return tb->cursor.offset;
|
||||
}
|
||||
|
||||
usize text_buffer_cursor_move_delta(TextBuffer* tb, CoordType cursor_movements)
|
||||
{
|
||||
if (cursor_movements < 0) {
|
||||
const usize offset = tb->cursor.offset;
|
||||
text_buffer_cursor_move_to_logical(tb, (Point){tb->cursor.logical_pos.x - 1, tb->cursor.logical_pos.y});
|
||||
if (offset == tb->cursor.offset) {
|
||||
text_buffer_cursor_move_to_logical(tb, (Point){COORD_TYPE_SAFE_MAX, tb->cursor.logical_pos.y - 1});
|
||||
}
|
||||
} else if (cursor_movements > 0) {
|
||||
const usize offset = tb->cursor.offset;
|
||||
text_buffer_cursor_move_to_logical(tb, (Point){tb->cursor.logical_pos.x + 1, tb->cursor.logical_pos.y});
|
||||
if (offset == tb->cursor.offset) {
|
||||
text_buffer_cursor_move_to_logical(tb, (Point){0, tb->cursor.logical_pos.y + 1});
|
||||
}
|
||||
}
|
||||
return tb->cursor.offset;
|
||||
}
|
||||
|
||||
usize text_buffer_extract(TextBuffer* tb, usize beg, usize end, c8* dst)
|
||||
{
|
||||
assert(beg <= end && end <= tb->text_length);
|
||||
if (beg >= end || end > tb->text_length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
c8* buf_end = dst;
|
||||
|
||||
if (beg < tb->gap_off) {
|
||||
// s(tart), l(ength)
|
||||
usize s = beg;
|
||||
usize l = min(end, tb->gap_off) - beg;
|
||||
memcpy(buf_end, tb->text + s, l);
|
||||
buf_end += l;
|
||||
}
|
||||
|
||||
if (end > tb->gap_off) {
|
||||
usize s = max(beg, tb->gap_off);
|
||||
usize l = end - s;
|
||||
memcpy(buf_end, tb->text + tb->gap_len + s, l);
|
||||
buf_end += l;
|
||||
}
|
||||
|
||||
return buf_end - dst;
|
||||
}
|
||||
|
||||
static void text_buffer_apply_change(TextBuffer* tb, TextBufferChange* change)
|
||||
{
|
||||
tb->cursor = change->cursor;
|
||||
|
||||
// Since we'll be deleting the inserted portion, we only need to allocate the delta.
|
||||
usize gap_len = max(change->removed.len, change->inserted.len) - change->inserted.len;
|
||||
c8* dst = text_buffer_allocate_gap(tb, tb->cursor.offset, gap_len);
|
||||
|
||||
// excerpt of text_buffer_delete_right
|
||||
{
|
||||
usize count = change->inserted.len;
|
||||
#ifndef NDEBUG
|
||||
memset(tb->text + tb->gap_off + tb->gap_len, 0xDD, count);
|
||||
#endif
|
||||
tb->gap_len += count;
|
||||
tb->text_length -= count;
|
||||
}
|
||||
|
||||
// excerpt of text_buffer_insert
|
||||
{
|
||||
s8 str = change->removed;
|
||||
|
||||
memcpy(dst, str.beg, str.len);
|
||||
text_buffer_commit_gap(tb, str.len);
|
||||
|
||||
TextBufferCursor cursor_before = tb->cursor;
|
||||
text_buffer_cursor_move_delta(tb, -1);
|
||||
usize backoff_count = cursor_before.offset - tb->cursor.offset;
|
||||
|
||||
s8 chunk = text_buffer_read_forward(tb, tb->cursor.offset);
|
||||
chunk = s8_slice(chunk, 0, backoff_count + str.len);
|
||||
UcdMeasurement after = ucd_measure_forward(chunk, 0, tb->cursor.logical_pos, COORD_TYPE_MAX, COORD_TYPE_MAX, NULL);
|
||||
tb->cursor.offset += after.offset;
|
||||
tb->cursor.logical_pos = after.pos;
|
||||
}
|
||||
}
|
||||
|
||||
void text_buffer_undo(TextBuffer* tb)
|
||||
{
|
||||
if (!tb->undo_tail) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextBufferChange* change = tb->undo_tail;
|
||||
tb->undo_tail = change->prev;
|
||||
|
||||
text_buffer_apply_change(tb, change);
|
||||
|
||||
s8 removed = change->removed;
|
||||
s8 inserted = change->inserted;
|
||||
change->cursor = tb->cursor;
|
||||
change->removed = inserted;
|
||||
change->inserted = removed;
|
||||
}
|
||||
|
||||
void text_buffer_redo(TextBuffer* tb)
|
||||
{
|
||||
TextBufferChange* change = tb->undo_tail;
|
||||
if (!change) {
|
||||
change = tb->undo_base;
|
||||
if (!change) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
change = change->next;
|
||||
if (!change) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextBufferCursor cursor = tb->cursor;
|
||||
tb->undo_tail = change;
|
||||
|
||||
text_buffer_apply_change(tb, change);
|
||||
|
||||
s8 removed = change->removed;
|
||||
s8 inserted = change->inserted;
|
||||
change->cursor = cursor;
|
||||
change->removed = inserted;
|
||||
change->inserted = removed;
|
||||
}
|
||||
|
||||
void text_buffer_write(TextBuffer* tb, s8 str)
|
||||
{
|
||||
if (str.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
c8* dst = text_buffer_allocate_gap(tb, tb->cursor.offset, str.len);
|
||||
memcpy(dst, str.beg, str.len);
|
||||
|
||||
s8 text = {tb->text, tb->gap_off + str.len, tb->gap_off + str.len};
|
||||
UcdMeasurement prev_bck;
|
||||
UcdMeasurement prev_fwd;
|
||||
if (tb->gap_off == 0 || tb->text[tb->gap_off - 1] == '\t') {
|
||||
prev_bck.offset = tb->gap_off;
|
||||
prev_bck.pos = tb->cursor.logical_pos;
|
||||
prev_bck.movements = 0;
|
||||
prev_fwd = prev_bck;
|
||||
} else {
|
||||
prev_bck = ucd_measure_backward(text, tb->gap_off, tb->cursor.logical_pos, -1, 1);
|
||||
prev_fwd = ucd_measure_forward(text, prev_bck.offset, prev_bck.pos, -1, 1, NULL);
|
||||
}
|
||||
UcdMeasurement next = ucd_measure_forward(text, prev_fwd.offset, prev_fwd.pos, -1, -1, NULL);
|
||||
|
||||
usize off_beg = tb->cursor.offset;
|
||||
usize off_end = tb->cursor.offset;
|
||||
|
||||
if (tb->overtype) {
|
||||
UcdMeasurement fwd = ucd_measure_forward(text_buffer_read_forward(tb, tb->cursor.offset), 0, tb->cursor.logical_pos, COORD_TYPE_MAX, next.movements, NULL);
|
||||
bool combines_with_preceding = prev_fwd.offset != tb->gap_off;
|
||||
off_beg = combines_with_preceding ? prev_bck.offset : prev_fwd.offset;
|
||||
off_end = tb->gap_off + fwd.offset;
|
||||
}
|
||||
|
||||
text_buffer_record_push_change(tb, off_beg, off_end, str);
|
||||
text_buffer_commit_gap(tb, str.len);
|
||||
|
||||
tb->gap_len += off_end - off_beg;
|
||||
tb->cursor.offset = next.offset;
|
||||
tb->cursor.logical_pos = next.pos;
|
||||
}
|
||||
|
||||
void text_buffer_delete(TextBuffer* tb, CoordType cursor_movements)
|
||||
{
|
||||
TextBufferCursor cursor_beg = tb->cursor;
|
||||
text_buffer_cursor_move_delta(tb, cursor_movements);
|
||||
TextBufferCursor cursor_end = tb->cursor;
|
||||
|
||||
if (cursor_beg.offset == cursor_end.offset) {
|
||||
return;
|
||||
}
|
||||
if (cursor_beg.offset > cursor_end.offset) {
|
||||
TextBufferCursor tmp = cursor_beg;
|
||||
cursor_beg = cursor_end;
|
||||
cursor_end = tmp;
|
||||
}
|
||||
|
||||
tb->cursor = cursor_beg;
|
||||
|
||||
text_buffer_allocate_gap(tb, cursor_beg.offset, 0);
|
||||
text_buffer_record_push_change(tb, cursor_beg.offset, cursor_end.offset, (s8){});
|
||||
|
||||
usize count = cursor_end.offset - cursor_beg.offset;
|
||||
#ifndef NDEBUG
|
||||
memset(tb->text + tb->gap_off + tb->gap_len, 0xDD, count);
|
||||
#endif
|
||||
tb->gap_len += count;
|
||||
tb->text_length -= count;
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arena.h"
|
||||
|
||||
typedef struct TextBufferStatistics {
|
||||
CoordType lines;
|
||||
} TextBufferStatistics;
|
||||
|
||||
typedef struct TextBufferCursor {
|
||||
usize offset;
|
||||
Point logical_pos; // in lines & graphemes; line wrapping has no influence on this
|
||||
Point visual_pos; // in rows & columns, including line wrapping
|
||||
} TextBufferCursor;
|
||||
|
||||
typedef enum TextBufferSelectionState {
|
||||
TEXT_BUFFER_SELECTION_NONE,
|
||||
TEXT_BUFFER_SELECTION_MAYBE,
|
||||
TEXT_BUFFER_SELECTION_ACTIVE,
|
||||
TEXT_BUFFER_SELECTION_DONE,
|
||||
} TextBufferSelectionState;
|
||||
|
||||
typedef struct TextBufferSelection {
|
||||
Point beg; // inclusive
|
||||
Point end; // inclusive
|
||||
TextBufferSelectionState state;
|
||||
} TextBufferSelection;
|
||||
|
||||
typedef struct TextBufferChange TextBufferChange;
|
||||
|
||||
struct TextBufferChange {
|
||||
TextBufferChange* prev;
|
||||
TextBufferChange* next;
|
||||
TextBufferCursor cursor;
|
||||
s8 removed; // The text that was removed.
|
||||
s8 inserted; // The text that was inserted.
|
||||
};
|
||||
|
||||
typedef struct TextBufferAllocator TextBufferAllocator;
|
||||
|
||||
struct TextBufferAllocator {
|
||||
void* (*realloc)(void* base, usize old_len, usize* new_len);
|
||||
void (*free)(void* base, usize len);
|
||||
};
|
||||
|
||||
typedef struct TextBuffer {
|
||||
TextBufferAllocator allocator;
|
||||
|
||||
c8* main_base;
|
||||
usize main_commit;
|
||||
|
||||
c8* text;
|
||||
usize text_length; // does not account for the gap_len
|
||||
usize gap_off;
|
||||
usize gap_len;
|
||||
|
||||
void* undo_base;
|
||||
TextBufferChange* undo_tail;
|
||||
usize undo_usage;
|
||||
usize undo_commit;
|
||||
|
||||
TextBufferStatistics stats;
|
||||
TextBufferCursor cursor;
|
||||
TextBufferSelection selection;
|
||||
|
||||
CoordType word_wrap_columns;
|
||||
bool dirty;
|
||||
bool overtype;
|
||||
} TextBuffer;
|
||||
|
||||
TextBufferAllocator text_buffer_allocator_default();
|
||||
|
||||
TextBuffer* text_buffer_create(TextBufferAllocator allocator);
|
||||
void text_buffer_destroy(TextBuffer* tb);
|
||||
void text_buffer_read_file(TextBuffer* tb, s8 path);
|
||||
void text_buffer_write_file(TextBuffer* tb, s8 path);
|
||||
|
||||
s8 text_buffer_read_backward(const TextBuffer* tb, usize off);
|
||||
s8 text_buffer_read_forward(const TextBuffer* tb, usize off);
|
||||
|
||||
void text_buffer_selection_update(TextBuffer* tb, Point pos);
|
||||
bool text_buffer_selection_end(TextBuffer* tb);
|
||||
|
||||
void text_buffer_reflow(TextBuffer* tb, CoordType width);
|
||||
usize text_buffer_cursor_move_to_logical(TextBuffer* tb, Point pos);
|
||||
usize text_buffer_cursor_move_to_visual(TextBuffer* tb, Point pos);
|
||||
usize text_buffer_cursor_move_delta(TextBuffer* tb, CoordType cursor_movements);
|
||||
usize text_buffer_extract(TextBuffer* tb, usize beg, usize end, c8* dst);
|
||||
|
||||
void text_buffer_undo(TextBuffer* tb);
|
||||
void text_buffer_redo(TextBuffer* tb);
|
||||
|
||||
void text_buffer_write(TextBuffer* tb, s8 str);
|
||||
void text_buffer_delete(TextBuffer* tb, CoordType cursor_movements);
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
bool rect_is_empty(Rect rect)
|
||||
{
|
||||
return rect.left >= rect.right || rect.top >= rect.bottom;
|
||||
}
|
||||
|
||||
bool rect_contains(Rect rect, Point point)
|
||||
{
|
||||
return point.x >= rect.left && point.x < rect.right && point.y >= rect.top && point.y < rect.bottom;
|
||||
}
|
||||
|
||||
Rect rect_intersect(Rect lhs, Rect rhs)
|
||||
{
|
||||
#if 1
|
||||
CoordType l = max(lhs.left, rhs.left);
|
||||
CoordType t = max(lhs.top, rhs.top);
|
||||
CoordType r = min(lhs.right, rhs.right);
|
||||
CoordType b = min(lhs.bottom, rhs.bottom);
|
||||
|
||||
// Ensure that the size is non-negative. This avoids bugs,
|
||||
// because some height/width is negative all of a sudden.
|
||||
r = max(l, r);
|
||||
b = max(t, b);
|
||||
|
||||
return (Rect){l, t, r, b};
|
||||
#else
|
||||
// I wrote some SSE2 code for fun. It's a lot faster and more compact than the scalar code.
|
||||
// I've tested it a little bit, and I believe it's correct. It's left disabled until I'm at
|
||||
// a point were vectorization can be justified (= the editor not being complete trash).
|
||||
__m128i a = _mm_loadu_si128((__m128i*)&lhs);
|
||||
__m128i b = _mm_loadu_si128((__m128i*)&rhs);
|
||||
|
||||
// Compute the min of left/top and max of right/bottom.
|
||||
__m128i lt = _mm_max_epi32(a, b);
|
||||
__m128i rb = _mm_min_epi32(a, b);
|
||||
|
||||
// Duplicate the min() of left/top into the upper half and...
|
||||
lt = _mm_shuffle_epi32(lt, _MM_SHUFFLE(1, 0, 1, 0));
|
||||
// ...ensure that the size is non-negative by ensuring that
|
||||
// the upper half of `rb` is at least as large as `lt`.
|
||||
rb = _mm_max_epi32(lt, rb);
|
||||
|
||||
// Combine the lower half `lt` (max of left/top) and
|
||||
// the upper half of `rb` (min of right/bottom).
|
||||
__m128i res = _mm_blend_epi16(lt, rb, 0xf0);
|
||||
|
||||
Rect result;
|
||||
_mm_storeu_si128((__m128i*)&result, res);
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
s8 s8_from_ptr(const c8* ptr)
|
||||
{
|
||||
usize len = strlen((const char*)ptr);
|
||||
return (s8){(c8*)ptr, len, len};
|
||||
}
|
||||
|
||||
s8 s8_slice(s8 str, usize beg, usize end)
|
||||
{
|
||||
end = min(end, str.len);
|
||||
beg = min(beg, end);
|
||||
return (s8){str.beg + beg, end - beg, end - beg};
|
||||
}
|
||||
|
||||
bool s8_starts_with(s8 str, s8 prefix)
|
||||
{
|
||||
return str.len >= prefix.len && memcmp(str.beg, prefix.beg, prefix.len) == 0;
|
||||
}
|
||||
|
||||
void s8_transform_lowercase_ascii(s8 str)
|
||||
{
|
||||
for (usize i = 0; i < str.len; ++i) {
|
||||
if (str.beg[i] >= 'A' && str.beg[i] <= 'Z') {
|
||||
str.beg[i] += 'a' - 'A';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usize s8_find(s8 str, usize off, c8 ch)
|
||||
{
|
||||
for (; off < str.len; ++off) {
|
||||
if (str.beg[off] == ch) {
|
||||
return off;
|
||||
}
|
||||
}
|
||||
return str.len;
|
||||
}
|
||||
|
||||
u64 s8_to_u64(s8 str, int base) {
|
||||
c8* ptr = str.beg;
|
||||
const c8* end = ptr + str.len;
|
||||
u64 accumulator = 0;
|
||||
u64 base64 = base;
|
||||
|
||||
if (base <= 0)
|
||||
{
|
||||
base64 = 10;
|
||||
|
||||
if (ptr != end && *ptr == '0')
|
||||
{
|
||||
base64 = 8;
|
||||
ptr += 1;
|
||||
|
||||
if (ptr != end && (*ptr == 'x' || *ptr == 'X'))
|
||||
{
|
||||
base64 = 16;
|
||||
ptr += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr == end)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
u64 value = 0;
|
||||
if (*ptr >= '0' && *ptr <= '9')
|
||||
{
|
||||
value = *ptr - '0';
|
||||
}
|
||||
else if (*ptr >= 'A' && *ptr <= 'F')
|
||||
{
|
||||
value = *ptr - 'A' + 10;
|
||||
}
|
||||
else if (*ptr >= 'a' && *ptr <= 'f')
|
||||
{
|
||||
value = *ptr - 'a' + 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const u64 acc = accumulator * base64 + value;
|
||||
if (acc < accumulator)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
accumulator = acc;
|
||||
ptr += 1;
|
||||
|
||||
if (ptr == end)
|
||||
{
|
||||
return accumulator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static u64 wyr3(const u8* p, usize k)
|
||||
{
|
||||
return ((u64)p[0] << 16) | ((u64)p[k >> 1] << 8) | p[k - 1];
|
||||
}
|
||||
|
||||
static u64 wyr4(const u8* p)
|
||||
{
|
||||
uint32_t v;
|
||||
memcpy(&v, p, 4);
|
||||
return v;
|
||||
}
|
||||
|
||||
static u64 wyr8(const u8* p)
|
||||
{
|
||||
u64 v;
|
||||
memcpy(&v, p, 8);
|
||||
return v;
|
||||
}
|
||||
|
||||
static u64 wymix(u64 lhs, u64 rhs)
|
||||
{
|
||||
#if os_UNIX
|
||||
__uint128_t r = (__uint128_t)lhs * (__uint128_t)rhs;
|
||||
return (u64)(r >> 64) ^ (u64)r;
|
||||
#elif PLATFORM_X64
|
||||
extern u64 _umul128(u64 Multiplier, u64 Multiplicand, u64 * HighProduct);
|
||||
u64 hi = 0;
|
||||
u64 lo = _umul128(lhs, rhs, &hi);
|
||||
return lo ^ hi;
|
||||
#elif os_ARM64
|
||||
extern u64 __umulh(u64 a, u64 b);
|
||||
const u64 lo = lhs * rhs;
|
||||
const u64 hi = __umulh(lhs, rhs);
|
||||
return lo ^ hi;
|
||||
#endif
|
||||
}
|
||||
|
||||
// The venerable wyhash hash function. It's fast and has good statistical properties.
|
||||
// It's in the public domain.
|
||||
u64 hash(u64 seed, void* data, usize len)
|
||||
{
|
||||
static const u64 s0 = 0xa0761d6478bd642f;
|
||||
static const u64 s1 = 0xe7037ed1a0b428db;
|
||||
static const u64 s2 = 0x8ebc6af09c88c6e3;
|
||||
static const u64 s3 = 0x589965cc75374cc3;
|
||||
|
||||
const u8* p = data;
|
||||
seed ^= s0;
|
||||
u64 a;
|
||||
u64 b;
|
||||
|
||||
if (len <= 16) {
|
||||
if (len >= 4) {
|
||||
a = (wyr4(p) << 32) | wyr4(p + ((len >> 3) << 2));
|
||||
b = (wyr4(p + len - 4) << 32) | wyr4(p + len - 4 - ((len >> 3) << 2));
|
||||
} else if (len > 0) {
|
||||
a = wyr3(p, len);
|
||||
b = 0;
|
||||
} else {
|
||||
a = b = 0;
|
||||
}
|
||||
} else {
|
||||
usize i = len;
|
||||
if (i > 48) {
|
||||
u64 seed1 = seed;
|
||||
u64 seed2 = seed;
|
||||
do {
|
||||
seed = wymix(wyr8(p) ^ s1, wyr8(p + 8) ^ seed);
|
||||
seed1 = wymix(wyr8(p + 16) ^ s2, wyr8(p + 24) ^ seed1);
|
||||
seed2 = wymix(wyr8(p + 32) ^ s3, wyr8(p + 40) ^ seed2);
|
||||
p += 48;
|
||||
i -= 48;
|
||||
} while (i > 48);
|
||||
seed ^= seed1 ^ seed2;
|
||||
}
|
||||
while (i > 16) {
|
||||
seed = wymix(wyr8(p) ^ s1, wyr8(p + 8) ^ seed);
|
||||
i -= 16;
|
||||
p += 16;
|
||||
}
|
||||
a = wyr8(p + i - 16);
|
||||
b = wyr8(p + i - 8);
|
||||
}
|
||||
|
||||
return wymix(s1 ^ len, wymix(a ^ s1, b ^ seed));
|
||||
}
|
||||
|
||||
u64 hash_s8(u64 seed, s8 str)
|
||||
{
|
||||
return hash(seed, str.beg, str.len);
|
||||
}
|
||||
|
||||
u64 u64_log10(u64 v)
|
||||
{
|
||||
// This implements https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 but with lzcnt for log2.
|
||||
static const u64 powers_of_10[] = {
|
||||
0u,
|
||||
10u,
|
||||
100u,
|
||||
1000u,
|
||||
10000u,
|
||||
100000u,
|
||||
1000000u,
|
||||
10000000u,
|
||||
100000000u,
|
||||
1000000000u,
|
||||
10000000000u,
|
||||
100000000000u,
|
||||
1000000000000u,
|
||||
10000000000000u,
|
||||
100000000000000u,
|
||||
1000000000000000u,
|
||||
10000000000000000u,
|
||||
100000000000000000u,
|
||||
1000000000000000000u,
|
||||
10000000000000000000u,
|
||||
};
|
||||
unsigned long index;
|
||||
#ifdef _MSC_VER
|
||||
_BitScanReverse64(&index, v | 1);
|
||||
#else
|
||||
index = 64 - __builtin_clzll(v | 1);
|
||||
#endif
|
||||
const u64 t = (index + 1) * 1233 >> 12;
|
||||
return t - (v < powers_of_10[t]);
|
||||
}
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef unreachable
|
||||
#define unreachable() __assume(false)
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#define os_WIN32 1
|
||||
#define attribute_forceinline __forceinline
|
||||
#define attribute_noinline __declspec(noinline)
|
||||
#else
|
||||
#define os_UNIX 1
|
||||
#define attribute_forceinline __attribute__((always_inline)) inline
|
||||
#define attribute_noinline __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC))
|
||||
#define PLATFORM_X64 1
|
||||
#elif defined(__aarch64__) || (defined(_M_ARM64) || defined(_M_ARM64EC))
|
||||
#define PLATFORM_ARM64 1
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
typedef int8_t i8;
|
||||
typedef uint8_t u8;
|
||||
typedef int16_t i16;
|
||||
typedef uint16_t u16;
|
||||
typedef int32_t i32;
|
||||
typedef uint32_t u32;
|
||||
typedef int64_t i64;
|
||||
typedef uint64_t u64;
|
||||
typedef ptrdiff_t isize;
|
||||
typedef size_t usize;
|
||||
typedef float f32;
|
||||
typedef double f64;
|
||||
#define USIZE_MAX ((usize) - 1)
|
||||
|
||||
typedef i32 CoordType;
|
||||
#define COORD_TYPE_MIN (-2147483647 - 1)
|
||||
#define COORD_TYPE_MAX 2147483647
|
||||
#define COORD_TYPE_SAFE_MIN (-32767 - 1)
|
||||
#define COORD_TYPE_SAFE_MAX 32767
|
||||
|
||||
#define array_size(a) (sizeof(a) / sizeof((a)[0]))
|
||||
|
||||
// clang-format off
|
||||
attribute_forceinline i32 min_i32(i32 a, i32 b) { return a < b ? a : b; }
|
||||
attribute_forceinline u32 min_u32(u32 a, u32 b) { return a < b ? a : b; }
|
||||
attribute_forceinline i64 min_i64(i64 a, i64 b) { return a < b ? a : b; }
|
||||
attribute_forceinline u64 min_u64(u64 a, u64 b) { return a < b ? a : b; }
|
||||
attribute_forceinline f32 min_f32(f32 a, f32 b) { return a < b ? a : b; }
|
||||
attribute_forceinline f64 min_f64(f64 a, f64 b) { return a < b ? a : b; }
|
||||
|
||||
attribute_forceinline i32 max_i32(i32 a, i32 b) { return a > b ? a : b; }
|
||||
attribute_forceinline u32 max_u32(u32 a, u32 b) { return a > b ? a : b; }
|
||||
attribute_forceinline i64 max_i64(i64 a, i64 b) { return a > b ? a : b; }
|
||||
attribute_forceinline u64 max_u64(u64 a, u64 b) { return a > b ? a : b; }
|
||||
attribute_forceinline f32 max_f32(f32 a, f32 b) { return a > b ? a : b; }
|
||||
attribute_forceinline f64 max_f64(f64 a, f64 b) { return a > b ? a : b; }
|
||||
|
||||
attribute_forceinline i32 clamp_i32(i32 v, i32 lo, i32 hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
||||
attribute_forceinline u32 clamp_u32(u32 v, u32 lo, u32 hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
||||
attribute_forceinline i64 clamp_i64(i64 v, i64 lo, i64 hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
||||
attribute_forceinline u64 clamp_u64(u64 v, u64 lo, u64 hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
||||
attribute_forceinline f32 clamp_f32(f32 v, f32 lo, f32 hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
||||
attribute_forceinline f64 clamp_f64(f64 v, f64 lo, f64 hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
||||
|
||||
#define min(a, b) _Generic((a), i32 : min_i32, u32 : min_u32, i64 : min_i64, u64 : min_u64, f32 : min_f32, f64 : min_f64)(a, b)
|
||||
#define max(a, b) _Generic((a), i32 : max_i32, u32 : max_u32, i64 : max_i64, u64 : max_u64, f32 : max_f32, f64 : max_f64)(a, b)
|
||||
#define clamp(v, lo, hi) _Generic((v), i32 : clamp_i32, u32 : clamp_u32, i64 : clamp_i64, u64 : clamp_u64, f32 : clamp_f32, f64 : clamp_f64)(v, lo, hi)
|
||||
// clang-format on
|
||||
|
||||
typedef struct Point {
|
||||
CoordType x;
|
||||
CoordType y;
|
||||
} Point;
|
||||
|
||||
typedef struct Size {
|
||||
CoordType width;
|
||||
CoordType height;
|
||||
} Size;
|
||||
|
||||
typedef struct Rect {
|
||||
CoordType left;
|
||||
CoordType top;
|
||||
CoordType right;
|
||||
CoordType bottom;
|
||||
} Rect;
|
||||
|
||||
bool rect_is_empty(Rect rect);
|
||||
bool rect_contains(Rect rect, Point point);
|
||||
Rect rect_intersect(Rect a, Rect b);
|
||||
|
||||
typedef u8 c8; // UTF-8 code unit
|
||||
typedef u16 c16; // UTF-16 code unit
|
||||
|
||||
// UTF-8 text
|
||||
typedef struct s8 {
|
||||
c8* beg;
|
||||
usize len;
|
||||
usize cap;
|
||||
} s8;
|
||||
|
||||
attribute_forceinline s8 S(const char* str)
|
||||
{
|
||||
usize len = strlen(str);
|
||||
return (s8){(c8*)str, len, len};
|
||||
}
|
||||
|
||||
s8 s8_from_ptr(const c8* ptr);
|
||||
s8 s8_slice(s8 str, usize beg, usize end);
|
||||
bool s8_starts_with(s8 str, s8 prefix);
|
||||
// NOTE: Calling s8_transform_lowercase_ascii on non-ASCII strings will result in garbage.
|
||||
void s8_transform_lowercase_ascii(s8 str);
|
||||
usize s8_find(s8 str, usize off, c8 ch);
|
||||
u64 s8_to_u64(s8 str, int base);
|
||||
|
||||
u64 hash(u64 seed, void* data, usize len);
|
||||
u64 hash_s8(u64 seed, s8 str);
|
||||
|
||||
u64 u64_log10(u64 v);
|
||||
|
||||
#define DOUBLY_LINKED_ENTRY(type) \
|
||||
type* prev; \
|
||||
type* next;
|
||||
|
||||
#define DOUBLY_LINKED_INIT(list, field) \
|
||||
(list)->field.prev = &(list)->field; \
|
||||
(list)->field.next = &(list)->field;
|
||||
|
||||
#define DOUBLY_LINKED_PUSH_TAIL(list, field, item) \
|
||||
do { \
|
||||
__typeof__(list) prev = (list)->field.prev; \
|
||||
entry->field.next = (list); \
|
||||
entry->field.prev = prev; \
|
||||
prev->field.next = item; \
|
||||
(list)->field.prev = entry; \
|
||||
} while (0)
|
||||
|
||||
#define DOUBLY_LINKED_INSERT_TAIL(head, obj, field) \
|
||||
(obj)->field.Flink = (head); \
|
||||
(obj)->field.Blink = (head)->field.Blink; \
|
||||
(head)->field.Blink->Flink = (obj); \
|
||||
(head)->field.Blink = (obj);
|
||||
|
||||
// Reference code for single/doubly linked lists with sentinels.
|
||||
// The problem with that is that it requires ugly offsetof() casts to get the owning structure.
|
||||
#if 0
|
||||
typedef struct SinglyLinkedEntry {
|
||||
struct SinglyLinkedEntry* next;
|
||||
} SinglyLinkedEntry;
|
||||
|
||||
attribute_forceinline SinglyLinkedEntry* singly_linked_pop_head(SinglyLinkedEntry* list)
|
||||
{
|
||||
SinglyLinkedEntry* entry = list->next;
|
||||
if (entry != NULL) {
|
||||
list->next = entry->next;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
attribute_forceinline void singly_linked_push_head(SinglyLinkedEntry* list, SinglyLinkedEntry* entry)
|
||||
{
|
||||
entry->next = list->next;
|
||||
list->next = entry;
|
||||
}
|
||||
|
||||
typedef struct DoublyLinkedEntry {
|
||||
struct DoublyLinkedEntry* Flink;
|
||||
struct DoublyLinkedEntry* Blink;
|
||||
} DoublyLinkedEntry;
|
||||
|
||||
attribute_forceinline void doubly_linked_init(DoublyLinkedEntry* list)
|
||||
{
|
||||
list->Flink = list->Blink = list;
|
||||
}
|
||||
|
||||
bool attribute_forceinline doubly_linked_is_empty(const DoublyLinkedEntry* list)
|
||||
{
|
||||
return list->Flink == list;
|
||||
}
|
||||
|
||||
attribute_forceinline void doubly_linked_remove_entry(DoublyLinkedEntry* entry)
|
||||
{
|
||||
DoublyLinkedEntry* Flink = entry->Flink;
|
||||
DoublyLinkedEntry* Blink = entry->Blink;
|
||||
Blink->Flink = Flink;
|
||||
Flink->Blink = Blink;
|
||||
}
|
||||
|
||||
attribute_forceinline DoublyLinkedEntry* doubly_linked_remove_head(DoublyLinkedEntry* list)
|
||||
{
|
||||
DoublyLinkedEntry* entry = list->Flink;
|
||||
DoublyLinkedEntry* Flink = entry->Flink;
|
||||
list->Flink = Flink;
|
||||
Flink->Blink = list;
|
||||
return entry;
|
||||
}
|
||||
|
||||
attribute_forceinline DoublyLinkedEntry* doubly_linked_remove_tail(DoublyLinkedEntry* list)
|
||||
{
|
||||
DoublyLinkedEntry* entry = list->Blink;
|
||||
DoublyLinkedEntry* Blink = entry->Blink;
|
||||
list->Blink = Blink;
|
||||
Blink->Flink = list;
|
||||
return entry;
|
||||
}
|
||||
|
||||
attribute_forceinline void doubly_linked_push_head(DoublyLinkedEntry* list, DoublyLinkedEntry* entry)
|
||||
{
|
||||
DoublyLinkedEntry* Flink = list->Flink;
|
||||
entry->Flink = Flink;
|
||||
entry->Blink = list;
|
||||
Flink->Blink = entry;
|
||||
list->Flink = entry;
|
||||
}
|
||||
|
||||
attribute_forceinline void doubly_linked_push_tail(DoublyLinkedEntry* list, DoublyLinkedEntry* entry)
|
||||
{
|
||||
DoublyLinkedEntry* Blink = list->Blink;
|
||||
entry->Flink = list;
|
||||
entry->Blink = Blink;
|
||||
Blink->Flink = entry;
|
||||
list->Blink = entry;
|
||||
}
|
||||
|
||||
attribute_forceinline void doubly_linked_append_list(DoublyLinkedEntry* list, DoublyLinkedEntry* append)
|
||||
{
|
||||
DoublyLinkedEntry* end = list->Blink;
|
||||
list->Blink->Flink = append;
|
||||
list->Blink = append->Blink;
|
||||
append->Blink->Flink = list;
|
||||
append->Blink = end;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,431 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "icu.h"
|
||||
|
||||
#include <icu.h>
|
||||
|
||||
#include "buffer.h"
|
||||
#include "os.h"
|
||||
|
||||
static int s_icu_ok = -1;
|
||||
static UText*(U_EXPORT2* fn_utext_close)(UText* ut);
|
||||
static UText*(U_EXPORT2* fn_utext_setup)(UText* ut, int32_t extraSpace, UErrorCode* status);
|
||||
static int32_t(U_EXPORT2* fn_u_strlen)(const UChar* s);
|
||||
static char*(U_EXPORT2* fn_u_strToUTF8WithSub)(char* dest, int32_t destCapacity, int32_t* pDestLength, const UChar* src, int32_t srcLength, UChar32 subchar, int32_t* pNumSubstitutions, UErrorCode* pErrorCode);
|
||||
static UChar*(U_EXPORT2* fn_u_strFromUTF8WithSub)(UChar* dest, int32_t destCapacity, int32_t* pDestLength, const char* src, int32_t srcLength, UChar32 subchar, int32_t* pNumSubstitutions, UErrorCode* pErrorCode);
|
||||
static UConverter*(U_EXPORT2* fn_ucnv_open)(const char* converterName, UErrorCode* err);
|
||||
static void(U_EXPORT2* fn_ucnv_close)(UConverter* converter);
|
||||
static const char*(U_EXPORT2* fn_ucnv_detectUnicodeSignature)(const char* source, int32_t sourceLength, int32_t* signatureLength, UErrorCode* pErrorCode);
|
||||
static void(U_EXPORT2* fn_ucnv_convertEx)(UConverter* targetCnv, UConverter* sourceCnv, char** target, const char* targetLimit, const char** source, const char* sourceLimit, UChar* pivotStart, UChar** pivotSource, UChar** pivotTarget, const UChar* pivotLimit, UBool reset, UBool flush, UErrorCode* pErrorCode);
|
||||
|
||||
/**
|
||||
* Function type declaration for UText.clone().
|
||||
*
|
||||
* clone a UText. Much like opening a UText where the source text is itself
|
||||
* another UText.
|
||||
*
|
||||
* A deep clone will copy both the UText data structures and the underlying text.
|
||||
* The original and cloned UText will operate completely independently; modifications
|
||||
* made to the text in one will not effect the other. Text providers are not
|
||||
* required to support deep clones. The user of clone() must check the status return
|
||||
* and be prepared to handle failures.
|
||||
*
|
||||
* A shallow clone replicates only the UText data structures; it does not make
|
||||
* a copy of the underlying text. Shallow clones can be used as an efficient way to
|
||||
* have multiple iterators active in a single text string that is not being
|
||||
* modified.
|
||||
*
|
||||
* A shallow clone operation must not fail except for truly exceptional conditions such
|
||||
* as memory allocation failures.
|
||||
*
|
||||
* A UText and its clone may be safely concurrently accessed by separate threads.
|
||||
* This is true for both shallow and deep clones.
|
||||
* It is the responsibility of the Text Provider to ensure that this thread safety
|
||||
* constraint is met.
|
||||
|
||||
*
|
||||
* @param dest A UText struct to be filled in with the result of the clone operation,
|
||||
* or NULL if the clone function should heap-allocate a new UText struct.
|
||||
* @param src The UText to be cloned.
|
||||
* @param deep true to request a deep clone, false for a shallow clone.
|
||||
* @param status Errors are returned here. For deep clones, U_UNSUPPORTED_ERROR
|
||||
* should be returned if the text provider is unable to clone the
|
||||
* original text.
|
||||
* @return The newly created clone, or NULL if the clone operation failed.
|
||||
*
|
||||
* @stable ICU 3.4
|
||||
*/
|
||||
static UText* U_CALLCONV icu_text_buffer_UTextClone(UText* dest, const UText* src, UBool deep, UErrorCode* status)
|
||||
{
|
||||
if (U_FAILURE(*status)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (deep) {
|
||||
*status = U_UNSUPPORTED_ERROR;
|
||||
return dest;
|
||||
}
|
||||
|
||||
dest = fn_utext_setup(dest, 4096, status);
|
||||
if (U_FAILURE(*status)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dest->providerProperties = src->providerProperties;
|
||||
dest->chunkNativeLimit = src->chunkNativeLimit;
|
||||
dest->extraSize = src->extraSize;
|
||||
dest->nativeIndexingLimit = src->nativeIndexingLimit;
|
||||
dest->chunkNativeStart = src->chunkNativeStart;
|
||||
dest->chunkOffset = src->chunkOffset;
|
||||
dest->chunkLength = src->chunkLength;
|
||||
dest->chunkContents = dest->pExtra;
|
||||
dest->pFuncs = src->pFuncs;
|
||||
dest->context = src->context;
|
||||
dest->p = src->p;
|
||||
dest->q = src->q;
|
||||
dest->r = src->r;
|
||||
dest->a = src->a;
|
||||
dest->b = src->b;
|
||||
dest->c = src->c;
|
||||
memcpy(dest->pExtra, src->pExtra, 4096);
|
||||
return dest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function type declaration for UText.nativeLength().
|
||||
*
|
||||
* @param ut the UText to get the length of.
|
||||
* @return the length, in the native units of the original text string.
|
||||
* @see UText
|
||||
* @stable ICU 3.4
|
||||
*/
|
||||
static int64_t U_CALLCONV icu_text_buffer_UTextNativeLength(UText* ut)
|
||||
{
|
||||
TextBuffer* tb = (TextBuffer*)ut->context;
|
||||
return tb->text_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function type declaration for UText.access(). Get the description of the text chunk
|
||||
* containing the text at a requested native index. The UText's iteration
|
||||
* position will be left at the requested index. If the index is out
|
||||
* of bounds, the iteration position will be left at the start or end
|
||||
* of the string, as appropriate.
|
||||
*
|
||||
* Chunks must begin and end on code point boundaries. A single code point
|
||||
* comprised of multiple storage units must never span a chunk boundary.
|
||||
*
|
||||
*
|
||||
* @param ut the UText being accessed.
|
||||
* @param nativeIndex Requested index of the text to be accessed.
|
||||
* @param forward If true, then the returned chunk must contain text
|
||||
* starting from the index, so that start<=index<limit.
|
||||
* If false, then the returned chunk must contain text
|
||||
* before the index, so that start<index<=limit.
|
||||
* @return True if the requested index could be accessed. The chunk
|
||||
* will contain the requested text.
|
||||
* False value if a chunk cannot be accessed
|
||||
* (the requested index is out of bounds).
|
||||
*
|
||||
* @see UText
|
||||
* @stable ICU 3.4
|
||||
*/
|
||||
static UBool U_CALLCONV icu_text_buffer_UTextAccess(UText* ut, int64_t nativeIndex, UBool forward)
|
||||
{
|
||||
TextBuffer* tb = (TextBuffer*)ut->context;
|
||||
i64 index_contained = nativeIndex;
|
||||
|
||||
if (!forward) {
|
||||
index_contained -= 1;
|
||||
}
|
||||
if (index_contained < 0 || (usize)index_contained >= tb->text_length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index_contained >= ut->chunkNativeStart && index_contained < ut->chunkNativeLimit) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
s8 text;
|
||||
i64 native_start;
|
||||
i64 native_limit;
|
||||
|
||||
if (forward) {
|
||||
text = text_buffer_read_forward(tb, nativeIndex);
|
||||
text = s8_slice(text, 0, 2048);
|
||||
native_start = nativeIndex;
|
||||
native_limit = nativeIndex + text.len;
|
||||
} else {
|
||||
text = text_buffer_read_backward(tb, nativeIndex);
|
||||
text = s8_slice(text, text.len - min(text.len, 2048), text.len);
|
||||
native_start = nativeIndex - text.len;
|
||||
native_limit = nativeIndex;
|
||||
}
|
||||
|
||||
// The utf16_buffer is 4096 bytes long = 2048 UChars.
|
||||
// The worst case scenario is that the text is ASCII (1 UChars per byte),
|
||||
// which means we can safely convert 2048 bytes to 2048 UChars.
|
||||
UChar* utf16_buffer = ut->pExtra;
|
||||
i32 utf16_buffer_len = 0;
|
||||
fn_u_strFromUTF8WithSub(utf16_buffer, 2048, &utf16_buffer_len, (const char*)text.beg, (i32)text.len, 0xfffd, NULL, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
ut->chunkContents = utf16_buffer;
|
||||
ut->chunkLength = utf16_buffer_len;
|
||||
ut->chunkOffset = forward ? 0 : utf16_buffer_len;
|
||||
ut->chunkNativeStart = native_start;
|
||||
ut->chunkNativeLimit = native_limit;
|
||||
ut->p = text.beg;
|
||||
ut->a = text.len;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int32_t U_CALLCONV icu_text_buffer_UTextExtract(UText* ut, int64_t nativeStart, int64_t nativeLimit, UChar* dest, int32_t destCapacity, UErrorCode* status)
|
||||
{
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function type declaration for UText.replace().
|
||||
*
|
||||
* Replace a range of the original text with a replacement text.
|
||||
*
|
||||
* Leaves the current iteration position at the position following the
|
||||
* newly inserted replacement text.
|
||||
*
|
||||
* This function need only be implemented on UText types that support writing.
|
||||
*
|
||||
* When using this function, there should be only a single UText opened onto the
|
||||
* underlying native text string. The function is responsible for updating the
|
||||
* text chunk within the UText to reflect the updated iteration position,
|
||||
* taking into account any changes to the underlying string's structure caused
|
||||
* by the replace operation.
|
||||
*
|
||||
* @param ut the UText representing the text to be operated on.
|
||||
* @param nativeStart the index of the start of the region to be replaced
|
||||
* @param nativeLimit the index of the character following the region to be replaced.
|
||||
* @param replacementText pointer to the replacement text
|
||||
* @param replacmentLength length of the replacement text in UChars, or -1 if the text is NUL terminated.
|
||||
* @param status receives any error status. Possible errors include
|
||||
* U_NO_WRITE_PERMISSION
|
||||
*
|
||||
* @return The signed number of (native) storage units by which
|
||||
* the length of the text expanded or contracted.
|
||||
*
|
||||
* @stable ICU 3.4
|
||||
*/
|
||||
static int32_t U_CALLCONV icu_text_buffer_UTextReplace(UText* ut, int64_t nativeStart, int64_t nativeLimit, const UChar* replacementText, int32_t replacmentLength, UErrorCode* status)
|
||||
{
|
||||
TextBuffer* tb = (TextBuffer*)ut->context;
|
||||
|
||||
if (nativeStart < 0 || (usize)nativeStart > tb->text_length) {
|
||||
return 0;
|
||||
}
|
||||
if (nativeLimit < nativeStart) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (replacmentLength == -1) {
|
||||
replacmentLength = fn_u_strlen(replacementText);
|
||||
}
|
||||
|
||||
if ((usize)nativeLimit > tb->text_length) {
|
||||
nativeLimit = tb->text_length;
|
||||
}
|
||||
if (nativeStart == nativeLimit && replacmentLength == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(false); // TODO: Implement this function.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function type declaration for UText.mapOffsetToNative().
|
||||
* Map from the current UChar offset within the current text chunk to
|
||||
* the corresponding native index in the original source text.
|
||||
*
|
||||
* This is required only for text providers that do not use native UTF-16 indexes.
|
||||
*
|
||||
* @param ut the UText.
|
||||
* @return Absolute (native) index corresponding to chunkOffset in the current chunk.
|
||||
* The returned native index should always be to a code point boundary.
|
||||
*
|
||||
* @stable ICU 3.4
|
||||
*/
|
||||
static int64_t U_CALLCONV icu_text_buffer_UTextMapOffsetToNative(const UText* ut)
|
||||
{
|
||||
const c8* beg = ut->p;
|
||||
i64 len = ut->a;
|
||||
i64 native = 0;
|
||||
i32 offset = 0;
|
||||
|
||||
while (offset < ut->chunkOffset) {
|
||||
u32 c;
|
||||
U8_NEXT_OR_FFFD(beg, native, len, c);
|
||||
offset += c >= 0x10000 ? 2 : 1;
|
||||
}
|
||||
|
||||
assert(offset == ut->chunkOffset);
|
||||
return native + ut->chunkNativeStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function type declaration for UText.mapIndexToUTF16().
|
||||
* Map from a native index to a UChar offset within a text chunk.
|
||||
* Behavior is undefined if the native index does not fall within the
|
||||
* current chunk.
|
||||
*
|
||||
* This function is required only for text providers that do not use native UTF-16 indexes.
|
||||
*
|
||||
* @param ut The UText containing the text chunk.
|
||||
* @param nativeIndex Absolute (native) text index, chunk->start<=index<=chunk->limit.
|
||||
* @return Chunk-relative UTF-16 offset corresponding to the specified native
|
||||
* index.
|
||||
*
|
||||
* @stable ICU 3.4
|
||||
*/
|
||||
static int32_t U_CALLCONV icu_text_buffer_UTextMapNativeIndexToUTF16(const UText* ut, int64_t nativeIndex)
|
||||
{
|
||||
i32 length;
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
i32 off = (i32)(nativeIndex - ut->chunkNativeStart);
|
||||
assert(off >= 0);
|
||||
fn_u_strFromUTF8WithSub(NULL, 0, &length, ut->p, off, 0xfffd, NULL, &status);
|
||||
return length;
|
||||
}
|
||||
|
||||
static const UTextFuncs s_text_funcs = {
|
||||
.tableSize = sizeof(UTextFuncs),
|
||||
.clone = icu_text_buffer_UTextClone,
|
||||
.nativeLength = icu_text_buffer_UTextNativeLength,
|
||||
.access = icu_text_buffer_UTextAccess,
|
||||
.extract = icu_text_buffer_UTextExtract,
|
||||
.replace = icu_text_buffer_UTextReplace,
|
||||
.mapOffsetToNative = icu_text_buffer_UTextMapOffsetToNative,
|
||||
.mapNativeIndexToUTF16 = icu_text_buffer_UTextMapNativeIndexToUTF16,
|
||||
};
|
||||
|
||||
void icu_init()
|
||||
{
|
||||
if (s_icu_ok != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
void* icu = os_load_library("icuuc.dll");
|
||||
if (icu == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_icu_ok = 1;
|
||||
|
||||
OS_SUPPRESS_GET_PROC_NAGGING_BEGIN
|
||||
fn_utext_close = os_get_proc_address(icu, "utext_close");
|
||||
s_icu_ok &= fn_utext_close != NULL;
|
||||
fn_utext_setup = os_get_proc_address(icu, "utext_setup");
|
||||
s_icu_ok &= fn_utext_setup != NULL;
|
||||
fn_u_strlen = os_get_proc_address(icu, "u_strlen");
|
||||
s_icu_ok &= fn_u_strlen != NULL;
|
||||
fn_u_strToUTF8WithSub = os_get_proc_address(icu, "u_strToUTF8WithSub");
|
||||
s_icu_ok &= fn_u_strToUTF8WithSub != NULL;
|
||||
fn_u_strFromUTF8WithSub = os_get_proc_address(icu, "u_strFromUTF8WithSub");
|
||||
s_icu_ok &= fn_u_strFromUTF8WithSub != NULL;
|
||||
fn_ucnv_open = os_get_proc_address(icu, "ucnv_open");
|
||||
s_icu_ok &= fn_ucnv_open != NULL;
|
||||
fn_ucnv_close = os_get_proc_address(icu, "ucnv_close");
|
||||
s_icu_ok &= fn_ucnv_close != NULL;
|
||||
fn_ucnv_detectUnicodeSignature = os_get_proc_address(icu, "ucnv_detectUnicodeSignature");
|
||||
s_icu_ok &= fn_ucnv_detectUnicodeSignature != NULL;
|
||||
fn_ucnv_convertEx = os_get_proc_address(icu, "ucnv_convertEx");
|
||||
s_icu_ok &= fn_ucnv_convertEx != NULL;
|
||||
OS_SUPPRESS_GET_PROC_NAGGING_END
|
||||
}
|
||||
|
||||
UText* text_buffer_utext(TextBuffer* tb)
|
||||
{
|
||||
icu_init();
|
||||
if (!s_icu_ok) {
|
||||
}
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UText* ut = fn_utext_setup(NULL, 4096, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ut->providerProperties = 0; // TODO: (1 << UTEXT_PROVIDER_WRITABLE)
|
||||
ut->pFuncs = &s_text_funcs;
|
||||
ut->context = tb;
|
||||
return ut;
|
||||
}
|
||||
|
||||
void text_buffer_utext_close(UText* ut)
|
||||
{
|
||||
fn_utext_close(ut);
|
||||
}
|
||||
|
||||
int read(char* buffer, usize length) { return -1; }
|
||||
|
||||
void write(const char* buffer, usize length) {}
|
||||
|
||||
const char* detectUnicodeSignature(const char* source, int32_t length)
|
||||
{
|
||||
icu_init();
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
const char* encoding = fn_ucnv_detectUnicodeSignature(source, length, NULL, &status);
|
||||
return U_SUCCESS(status) && encoding ? encoding : "UTF-8";
|
||||
}
|
||||
|
||||
void convertToUTF8(const char* encoding)
|
||||
{
|
||||
icu_init();
|
||||
|
||||
UErrorCode errorCode = U_ZERO_ERROR;
|
||||
UConverter* sourceCnv = fn_ucnv_open(encoding, &errorCode);
|
||||
UConverter* targetCnv = fn_ucnv_open("UTF-8", &errorCode);
|
||||
if (U_FAILURE(errorCode)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char sourceBuffer[1024];
|
||||
char targetBuffer[2048];
|
||||
UChar pivotBuffer[1024];
|
||||
const char* source;
|
||||
char* target;
|
||||
UChar* pivotSource = pivotBuffer;
|
||||
UChar* pivotTarget = pivotBuffer;
|
||||
const UChar* pivotLimit = pivotBuffer + 1024;
|
||||
bool reset = true;
|
||||
|
||||
for (;;) {
|
||||
int bytesRead = read(sourceBuffer, sizeof(sourceBuffer));
|
||||
source = sourceBuffer;
|
||||
const char* sourceLimit = sourceBuffer + bytesRead;
|
||||
target = targetBuffer;
|
||||
char* targetLimit = targetBuffer + sizeof(targetBuffer);
|
||||
|
||||
fn_ucnv_convertEx(targetCnv, sourceCnv, &target, targetLimit, &source, sourceLimit, pivotBuffer, &pivotSource, &pivotTarget, pivotLimit, reset, bytesRead <= 0, &errorCode);
|
||||
if (U_FAILURE(errorCode)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Skip initial U+FEFF BOM?
|
||||
write(targetBuffer, target - targetBuffer);
|
||||
reset = false;
|
||||
|
||||
if (bytesRead == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
fn_ucnv_close(sourceCnv);
|
||||
fn_ucnv_close(targetCnv);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
struct UText;
|
||||
typedef struct UText UText;
|
||||
|
||||
struct TextBuffer;
|
||||
typedef struct TextBuffer TextBuffer;
|
||||
|
||||
void icu_init();
|
||||
//UText* text_buffer_utext(TextBuffer* tb);
|
||||
void text_buffer_utext_close(UText* ut);
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "input.h"
|
||||
|
||||
UiInput get_next_ui_input(VtParserState* vt_parser_state, s8* input)
|
||||
{
|
||||
UiInput result = {};
|
||||
c8* it = input->beg;
|
||||
c8* end = input->beg + input->len;
|
||||
|
||||
while (it != end) {
|
||||
it = vt_parse_next_token(vt_parser_state, it, end);
|
||||
switch (vt_parser_state->kind) {
|
||||
case VT_TOKEN_KIND_TEXT:
|
||||
result.type = UI_INPUT_TEXT;
|
||||
result.text = vt_parser_state->text;
|
||||
goto done;
|
||||
case VT_TOKEN_KIND_CTRL:
|
||||
switch (vt_parser_state->ctrl) {
|
||||
case VK_NULL:
|
||||
case VK_TAB:
|
||||
case VK_RETURN:
|
||||
result.type = UI_INPUT_KEYBOARD;
|
||||
result.keyboard.key = vt_parser_state->ctrl;
|
||||
goto done;
|
||||
case '\x01':
|
||||
case '\x02':
|
||||
case '\x03':
|
||||
case '\x04':
|
||||
case '\x05':
|
||||
case '\x06':
|
||||
case '\x07':
|
||||
case '\x08':
|
||||
// case '\x09': // VK_TAB
|
||||
case '\x0a':
|
||||
case '\x0b':
|
||||
case '\x0c':
|
||||
// case '\x0d': // VK_RETURN
|
||||
case '\x0e':
|
||||
case '\x0f':
|
||||
case '\x10':
|
||||
case '\x11':
|
||||
case '\x12':
|
||||
case '\x13':
|
||||
case '\x14':
|
||||
case '\x15':
|
||||
case '\x16':
|
||||
case '\x17':
|
||||
case '\x18':
|
||||
case '\x19':
|
||||
case '\x1a':
|
||||
// Ctrl+A-Z with a few exceptions, because e.g. Ctrl+I is VK_TAB.
|
||||
result.type = UI_INPUT_KEYBOARD;
|
||||
result.keyboard.key = (UiInputKeyboardKey)(vt_parser_state->ctrl | 0b1000000);
|
||||
result.keyboard.modifiers = KEYBOARD_MODIFIER_CTRL;
|
||||
goto done;
|
||||
case '\x7f':
|
||||
result.type = UI_INPUT_KEYBOARD;
|
||||
result.keyboard.key = VK_BACK;
|
||||
goto done;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case VT_TOKEN_KIND_ESC:
|
||||
if (vt_parser_state->esc >= ' ' && vt_parser_state->esc <= '~') {
|
||||
result.type = UI_INPUT_KEYBOARD;
|
||||
result.keyboard.key = vt_parser_state->esc;
|
||||
result.keyboard.modifiers = KEYBOARD_MODIFIER_ALT;
|
||||
goto done;
|
||||
}
|
||||
assert(false);
|
||||
break;
|
||||
case VT_TOKEN_KIND_SS3:
|
||||
if (vt_parser_state->ss3 >= 'P' && vt_parser_state->ss3 <= 'S') {
|
||||
result.type = UI_INPUT_KEYBOARD;
|
||||
result.keyboard.key = vt_parser_state->ss3 - 'P' + VK_F1;
|
||||
goto done;
|
||||
}
|
||||
assert(false);
|
||||
break;
|
||||
case VT_TOKEN_KIND_CSI:
|
||||
if (vt_parser_state->csi.final_byte <= 'H') {
|
||||
static const u8 lut[] = {
|
||||
['A' - 'A'] = VK_UP,
|
||||
['B' - 'A'] = VK_DOWN,
|
||||
['C' - 'A'] = VK_RIGHT,
|
||||
['D' - 'A'] = VK_LEFT,
|
||||
['F' - 'A'] = VK_END,
|
||||
['H' - 'A'] = VK_HOME,
|
||||
};
|
||||
u8 vk = lut[vt_parser_state->csi.final_byte - 'A'];
|
||||
if (vk) {
|
||||
result.type = UI_INPUT_KEYBOARD;
|
||||
result.keyboard.key = vk;
|
||||
goto parse_modifiers;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (vt_parser_state->csi.final_byte) {
|
||||
case '~': {
|
||||
static const u8 lut[] = {
|
||||
[1] = VK_HOME,
|
||||
[2] = VK_INSERT,
|
||||
[3] = VK_DELETE,
|
||||
[4] = VK_END,
|
||||
[5] = VK_PRIOR,
|
||||
[6] = VK_NEXT,
|
||||
[15] = VK_F5,
|
||||
[17] = VK_F6,
|
||||
[18] = VK_F7,
|
||||
[19] = VK_F8,
|
||||
[20] = VK_F9,
|
||||
[21] = VK_F10,
|
||||
[23] = VK_F11,
|
||||
[24] = VK_F12,
|
||||
[25] = VK_F13,
|
||||
[26] = VK_F14,
|
||||
[28] = VK_F15,
|
||||
[29] = VK_F16,
|
||||
[31] = VK_F17,
|
||||
[32] = VK_F18,
|
||||
[33] = VK_F19,
|
||||
[34] = VK_F20,
|
||||
};
|
||||
i32 p0 = vt_parser_state->csi.params[0];
|
||||
if (p0 <= array_size(lut)) {
|
||||
u8 vk = lut[p0];
|
||||
if (vk) {
|
||||
result.type = UI_INPUT_KEYBOARD;
|
||||
result.keyboard.key = vk;
|
||||
goto parse_modifiers;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'm':
|
||||
case 'M':
|
||||
if (vt_parser_state->csi.private_byte == '<') {
|
||||
i32 btn = vt_parser_state->csi.params[0];
|
||||
|
||||
result.type = UI_INPUT_MOUSE;
|
||||
result.mouse.action = MOUSE_ACTION_NONE;
|
||||
if (btn & 0x40) {
|
||||
result.mouse.action = MOUSE_ACTION_SCROLL;
|
||||
result.mouse.scroll.y += btn & 0x01 ? 3 : -3;
|
||||
} else if (vt_parser_state->csi.final_byte == 'M') {
|
||||
MouseAction actions[] = {MOUSE_ACTION_LEFT, MOUSE_ACTION_MIDDLE, MOUSE_ACTION_RIGHT, MOUSE_ACTION_NONE};
|
||||
result.mouse.action = actions[btn & 0x03];
|
||||
}
|
||||
|
||||
result.mouse.modifier = KEYBOARD_MODIFIER_NONE;
|
||||
result.mouse.modifier |= btn & 0x04 ? KEYBOARD_MODIFIER_SHIFT : 0;
|
||||
result.mouse.modifier |= btn & 0x08 ? KEYBOARD_MODIFIER_ALT : 0;
|
||||
result.mouse.modifier |= btn & 0x10 ? KEYBOARD_MODIFIER_CTRL : 0;
|
||||
|
||||
result.mouse.position.x = vt_parser_state->csi.params[1] - 1;
|
||||
result.mouse.position.y = vt_parser_state->csi.params[2] - 1;
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
switch (vt_parser_state->csi.params[0]) {
|
||||
case 8: // Window Size
|
||||
result.type = UI_INPUT_RESIZE;
|
||||
result.resize.width = max(vt_parser_state->csi.params[2], 1);
|
||||
result.resize.height = max(vt_parser_state->csi.params[1], 1);
|
||||
assert(result.resize.width > 0 && result.resize.height > 0);
|
||||
assert(result.resize.width < 32768 && result.resize.height < 32768);
|
||||
goto done;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
parse_modifiers:
|
||||
i32 p1 = max(vt_parser_state->csi.params[1] - 1, 0);
|
||||
if (p1 & 0x01) {
|
||||
result.keyboard.modifiers |= KEYBOARD_MODIFIER_SHIFT;
|
||||
}
|
||||
if (p1 & 0x02) {
|
||||
result.keyboard.modifiers |= KEYBOARD_MODIFIER_ALT;
|
||||
}
|
||||
if (p1 & 0x04) {
|
||||
result.keyboard.modifiers |= KEYBOARD_MODIFIER_CTRL;
|
||||
}
|
||||
|
||||
done:
|
||||
*input = s8_slice(*input, it - input->beg, USIZE_MAX);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "tui.h"
|
||||
#include "vt.h"
|
||||
|
||||
UiInput get_next_ui_input(VtParserState* vt_parser_state, s8* input);
|
||||
|
|
@ -0,0 +1,372 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "loc.h"
|
||||
|
||||
#include "arena.h"
|
||||
|
||||
#define L(s) {(c8*)s, sizeof(s) - 1}
|
||||
|
||||
// TODO: Move this to the OS/platform layer
|
||||
#define MUI_LANGUAGE_NAME 0x8 // Use ISO language (culture) name convention
|
||||
__declspec(dllimport) i32 __stdcall GetUserPreferredUILanguages(u32 dwFlags, u32* pulNumLanguages, c16* pwszLanguagesBuffer, u32* pcchLanguagesBuffer);
|
||||
|
||||
typedef enum LangId {
|
||||
// Base language. It's always the first one.
|
||||
LANG_en,
|
||||
|
||||
// Other languages. Sorted alphabetically.
|
||||
LANG_de,
|
||||
LANG_es,
|
||||
LANG_fr,
|
||||
LANG_it,
|
||||
LANG_ja,
|
||||
LANG_ko,
|
||||
LANG_pt_br,
|
||||
LANG_ru,
|
||||
LANG_zh_hans,
|
||||
LANG_zh_hant,
|
||||
|
||||
LANG_COUNT,
|
||||
} LangId;
|
||||
|
||||
static const s8 s_lang_lut[LOC_COUNT][LANG_COUNT] = {
|
||||
[LOC_Ctrl] = {
|
||||
[LANG_en] = L("Ctrl"),
|
||||
[LANG_de] = L("Strg"),
|
||||
[LANG_es] = L("Ctrl"),
|
||||
[LANG_fr] = L("Ctrl"),
|
||||
[LANG_it] = L("Ctrl"),
|
||||
[LANG_ja] = L("Ctrl"),
|
||||
[LANG_ko] = L("Ctrl"),
|
||||
[LANG_pt_br] = L("Ctrl"),
|
||||
[LANG_ru] = L("Ctrl"),
|
||||
[LANG_zh_hans] = L("Ctrl"),
|
||||
[LANG_zh_hant] = L("Ctrl"),
|
||||
},
|
||||
[LOC_Alt] = {
|
||||
[LANG_en] = L("Alt"),
|
||||
[LANG_de] = L("Alt"),
|
||||
[LANG_es] = L("Alt"),
|
||||
[LANG_fr] = L("Alt"),
|
||||
[LANG_it] = L("Alt"),
|
||||
[LANG_ja] = L("Alt"),
|
||||
[LANG_ko] = L("Alt"),
|
||||
[LANG_pt_br] = L("Alt"),
|
||||
[LANG_ru] = L("Alt"),
|
||||
[LANG_zh_hans] = L("Alt"),
|
||||
[LANG_zh_hant] = L("Alt"),
|
||||
},
|
||||
[LOC_Shift] = {
|
||||
[LANG_en] = L("Shift"),
|
||||
[LANG_de] = L("Umschalt"),
|
||||
[LANG_es] = L("Mayús"),
|
||||
[LANG_fr] = L("Maj"),
|
||||
[LANG_it] = L("Maiusc"),
|
||||
[LANG_ja] = L("Shift"),
|
||||
[LANG_ko] = L("Shift"),
|
||||
[LANG_pt_br] = L("Shift"),
|
||||
[LANG_ru] = L("Shift"),
|
||||
[LANG_zh_hans] = L("Shift"),
|
||||
[LANG_zh_hant] = L("Shift"),
|
||||
},
|
||||
|
||||
[LOC_File] = {
|
||||
[LANG_en] = L("File"),
|
||||
[LANG_de] = L("Datei"),
|
||||
[LANG_es] = L("Archivo"),
|
||||
[LANG_fr] = L("Fichier"),
|
||||
[LANG_it] = L("File"),
|
||||
[LANG_ja] = L("ファイル"),
|
||||
[LANG_ko] = L("파일"),
|
||||
[LANG_pt_br] = L("Arquivo"),
|
||||
[LANG_ru] = L("Файл"),
|
||||
[LANG_zh_hans] = L("文件"),
|
||||
[LANG_zh_hant] = L("檔案"),
|
||||
},
|
||||
[LOC_File_Save] = {
|
||||
[LANG_en] = L("Save"),
|
||||
[LANG_de] = L("Speichern"),
|
||||
[LANG_es] = L("Guardar"),
|
||||
[LANG_fr] = L("Enregistrer"),
|
||||
[LANG_it] = L("Salva"),
|
||||
[LANG_ja] = L("保存"),
|
||||
[LANG_ko] = L("저장"),
|
||||
[LANG_pt_br] = L("Salvar"),
|
||||
[LANG_ru] = L("Сохранить"),
|
||||
[LANG_zh_hans] = L("保存"),
|
||||
[LANG_zh_hant] = L("儲存"),
|
||||
},
|
||||
[LOC_File_Save_As] = {
|
||||
[LANG_en] = L("Save As"),
|
||||
[LANG_de] = L("Speichern unter"),
|
||||
[LANG_es] = L("Guardar Como"),
|
||||
[LANG_fr] = L("Enregistrer sous"),
|
||||
[LANG_it] = L("Salva come"),
|
||||
[LANG_ja] = L("名前を付けて保存"),
|
||||
[LANG_ko] = L("다른 이름으로 저장"),
|
||||
[LANG_pt_br] = L("Salvar Como"),
|
||||
[LANG_ru] = L("Сохранить как"),
|
||||
[LANG_zh_hans] = L("另存为"),
|
||||
[LANG_zh_hant] = L("另存新檔"),
|
||||
},
|
||||
[LOC_File_Exit] = {
|
||||
[LANG_en] = L("Exit"),
|
||||
[LANG_de] = L("Beenden"),
|
||||
[LANG_es] = L("Salir"),
|
||||
[LANG_fr] = L("Quitter"),
|
||||
[LANG_it] = L("Esci"),
|
||||
[LANG_ja] = L("終了"),
|
||||
[LANG_ko] = L("종료"),
|
||||
[LANG_pt_br] = L("Sair"),
|
||||
[LANG_ru] = L("Выход"),
|
||||
[LANG_zh_hans] = L("退出"),
|
||||
[LANG_zh_hant] = L("退出"),
|
||||
},
|
||||
|
||||
[LOC_Edit] = {
|
||||
[LANG_en] = L("Edit"),
|
||||
[LANG_de] = L("Bearbeiten"),
|
||||
[LANG_es] = L("Editar"),
|
||||
[LANG_fr] = L("Éditer"),
|
||||
[LANG_it] = L("Modifica"),
|
||||
[LANG_ja] = L("編集"),
|
||||
[LANG_ko] = L("편집"),
|
||||
[LANG_pt_br] = L("Editar"),
|
||||
[LANG_ru] = L("Правка"),
|
||||
[LANG_zh_hans] = L("编辑"),
|
||||
[LANG_zh_hant] = L("編輯"),
|
||||
},
|
||||
[LOC_Edit_Undo] = {
|
||||
[LANG_en] = L("Undo"),
|
||||
[LANG_de] = L("Rückgängig"),
|
||||
[LANG_es] = L("Deshacer"),
|
||||
[LANG_fr] = L("Annuler"),
|
||||
[LANG_it] = L("Annulla"),
|
||||
[LANG_ja] = L("元に戻す"),
|
||||
[LANG_ko] = L("실행 취소"),
|
||||
[LANG_pt_br] = L("Desfazer"),
|
||||
[LANG_ru] = L("Отменить"),
|
||||
[LANG_zh_hans] = L("撤销"),
|
||||
[LANG_zh_hant] = L("復原"),
|
||||
},
|
||||
[LOC_Edit_Redo] = {
|
||||
[LANG_en] = L("Redo"),
|
||||
[LANG_de] = L("Wiederholen"),
|
||||
[LANG_es] = L("Rehacer"),
|
||||
[LANG_fr] = L("Rétablir"),
|
||||
[LANG_it] = L("Ripeti"),
|
||||
[LANG_ja] = L("やり直し"),
|
||||
[LANG_ko] = L("다시 실행"),
|
||||
[LANG_pt_br] = L("Refazer"),
|
||||
[LANG_ru] = L("Повторить"),
|
||||
[LANG_zh_hans] = L("重做"),
|
||||
[LANG_zh_hant] = L("重做"),
|
||||
},
|
||||
[LOC_Edit_Cut] = {
|
||||
[LANG_en] = L("Cut"),
|
||||
[LANG_de] = L("Ausschneiden"),
|
||||
[LANG_es] = L("Cortar"),
|
||||
[LANG_fr] = L("Couper"),
|
||||
[LANG_it] = L("Taglia"),
|
||||
[LANG_ja] = L("切り取り"),
|
||||
[LANG_ko] = L("잘라내기"),
|
||||
[LANG_pt_br] = L("Cortar"),
|
||||
[LANG_ru] = L("Вырезать"),
|
||||
[LANG_zh_hans] = L("剪切"),
|
||||
[LANG_zh_hant] = L("剪下"),
|
||||
},
|
||||
[LOC_Edit_Copy] = {
|
||||
[LANG_en] = L("Copy"),
|
||||
[LANG_de] = L("Kopieren"),
|
||||
[LANG_es] = L("Copiar"),
|
||||
[LANG_fr] = L("Copier"),
|
||||
[LANG_it] = L("Copia"),
|
||||
[LANG_ja] = L("コピー"),
|
||||
[LANG_ko] = L("복사"),
|
||||
[LANG_pt_br] = L("Copiar"),
|
||||
[LANG_ru] = L("Копировать"),
|
||||
[LANG_zh_hans] = L("复制"),
|
||||
[LANG_zh_hant] = L("複製"),
|
||||
},
|
||||
[LOC_Edit_Paste] = {
|
||||
[LANG_en] = L("Paste"),
|
||||
[LANG_de] = L("Einfügen"),
|
||||
[LANG_es] = L("Pegar"),
|
||||
[LANG_fr] = L("Coller"),
|
||||
[LANG_it] = L("Incolla"),
|
||||
[LANG_ja] = L("貼り付け"),
|
||||
[LANG_ko] = L("붙여넣기"),
|
||||
[LANG_pt_br] = L("Colar"),
|
||||
[LANG_ru] = L("Вставить"),
|
||||
[LANG_zh_hans] = L("粘贴"),
|
||||
[LANG_zh_hant] = L("貼上"),
|
||||
},
|
||||
[LOC_Edit_Find] = {
|
||||
[LANG_en] = L("Find"),
|
||||
[LANG_de] = L("Suchen"),
|
||||
[LANG_es] = L("Buscar"),
|
||||
[LANG_fr] = L("Rechercher"),
|
||||
[LANG_it] = L("Trova"),
|
||||
[LANG_ja] = L("検索"),
|
||||
[LANG_ko] = L("찾기"),
|
||||
[LANG_pt_br] = L("Encontrar"),
|
||||
[LANG_ru] = L("Найти"),
|
||||
[LANG_zh_hans] = L("查找"),
|
||||
[LANG_zh_hant] = L("尋找"),
|
||||
},
|
||||
[LOC_Edit_Replace] = {
|
||||
[LANG_en] = L("Replace"),
|
||||
[LANG_de] = L("Ersetzen"),
|
||||
[LANG_es] = L("Reemplazar"),
|
||||
[LANG_fr] = L("Remplacer"),
|
||||
[LANG_it] = L("Sostituisci"),
|
||||
[LANG_ja] = L("置換"),
|
||||
[LANG_ko] = L("바꾸기"),
|
||||
[LANG_pt_br] = L("Substituir"),
|
||||
[LANG_ru] = L("Заменить"),
|
||||
[LANG_zh_hans] = L("替换"),
|
||||
[LANG_zh_hant] = L("取代"),
|
||||
},
|
||||
|
||||
[LOC_Help] = {
|
||||
[LANG_en] = L("Help"),
|
||||
[LANG_de] = L("Hilfe"),
|
||||
[LANG_es] = L("Ayuda"),
|
||||
[LANG_fr] = L("Aide"),
|
||||
[LANG_it] = L("Aiuto"),
|
||||
[LANG_ja] = L("ヘルプ"),
|
||||
[LANG_ko] = L("도움말"),
|
||||
[LANG_pt_br] = L("Ajuda"),
|
||||
[LANG_ru] = L("Помощь"),
|
||||
[LANG_zh_hans] = L("帮助"),
|
||||
[LANG_zh_hant] = L("幫助"),
|
||||
},
|
||||
[LOC_Help_About] = {
|
||||
[LANG_en] = L("About"),
|
||||
[LANG_de] = L("Über"),
|
||||
[LANG_es] = L("Acerca de"),
|
||||
[LANG_fr] = L("À propos"),
|
||||
[LANG_it] = L("Informazioni"),
|
||||
[LANG_ja] = L("情報"),
|
||||
[LANG_ko] = L("정보"),
|
||||
[LANG_pt_br] = L("Sobre"),
|
||||
[LANG_ru] = L("О программе"),
|
||||
[LANG_zh_hans] = L("关于"),
|
||||
[LANG_zh_hant] = L("關於"),
|
||||
},
|
||||
|
||||
[LOC_Exit_Dialog_Title] = {
|
||||
[LANG_en] = L("Exit without saving?"),
|
||||
[LANG_de] = L("Ohne Speichern beenden?"),
|
||||
[LANG_es] = L("¿Salir sin guardar?"),
|
||||
[LANG_fr] = L("Quitter sans enregistrer ?"),
|
||||
[LANG_it] = L("Uscire senza salvare?"),
|
||||
[LANG_ja] = L("保存せずに終了しますか?"),
|
||||
[LANG_ko] = L("저장하지 않고 종료하시겠습니까?"),
|
||||
[LANG_pt_br] = L("Sair sem salvar?"),
|
||||
[LANG_ru] = L("Выйти без сохранения?"),
|
||||
[LANG_zh_hans] = L("退出前是否保存?"),
|
||||
[LANG_zh_hant] = L("退出不儲存?"),
|
||||
},
|
||||
[LOC_Exit_Dialog_Yes] = {
|
||||
[LANG_en] = L("Yes"),
|
||||
[LANG_de] = L("Ja"),
|
||||
[LANG_es] = L("Sí"),
|
||||
[LANG_fr] = L("Oui"),
|
||||
[LANG_it] = L("Sì"),
|
||||
[LANG_ja] = L("はい"),
|
||||
[LANG_ko] = L("예"),
|
||||
[LANG_pt_br] = L("Sim"),
|
||||
[LANG_ru] = L("Да"),
|
||||
[LANG_zh_hans] = L("是"),
|
||||
[LANG_zh_hant] = L("是"),
|
||||
},
|
||||
[LOC_Exit_Dialog_No] = {
|
||||
[LANG_en] = L("No"),
|
||||
[LANG_de] = L("Nein"),
|
||||
[LANG_es] = L("No"),
|
||||
[LANG_fr] = L("Non"),
|
||||
[LANG_it] = L("No"),
|
||||
[LANG_ja] = L("いいえ"),
|
||||
[LANG_ko] = L("아니요"),
|
||||
[LANG_pt_br] = L("Não"),
|
||||
[LANG_ru] = L("Нет"),
|
||||
[LANG_zh_hans] = L("否"),
|
||||
[LANG_zh_hant] = L("否"),
|
||||
},
|
||||
};
|
||||
|
||||
static int s_lang;
|
||||
|
||||
void loc_init()
|
||||
{
|
||||
// Get the user's preferred UI language
|
||||
u32 lang_num = 0;
|
||||
c16 lang_buf[256] = {0};
|
||||
u32 lang_buf_len = array_size(lang_buf);
|
||||
if (!GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &lang_num, lang_buf, &lang_buf_len) || lang_num == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// transform to lowercase
|
||||
for (u32 i = 0; i < lang_buf_len; ++i) {
|
||||
if (lang_buf[i] >= 'A' && lang_buf[i] <= 'Z') {
|
||||
lang_buf[i] += 'a' - 'A';
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Iterate through the null-delimited list of language names using wcslen() and convert each to UTF-8. Don't use lang_num
|
||||
c16* beg = &lang_buf[0];
|
||||
for (; *beg; beg += wcslen(beg)) {
|
||||
if (wcsncmp(beg, L"en", 2) == 0) {
|
||||
s_lang = LANG_en;
|
||||
break;
|
||||
}
|
||||
if (wcsncmp(beg, L"de", 2) == 0) {
|
||||
s_lang = LANG_de;
|
||||
break;
|
||||
}
|
||||
if (wcsncmp(beg, L"es", 2) == 0) {
|
||||
s_lang = LANG_es;
|
||||
break;
|
||||
}
|
||||
if (wcsncmp(beg, L"fr", 2) == 0) {
|
||||
s_lang = LANG_fr;
|
||||
break;
|
||||
}
|
||||
if (wcsncmp(beg, L"it", 2) == 0) {
|
||||
s_lang = LANG_it;
|
||||
break;
|
||||
}
|
||||
if (wcsncmp(beg, L"ja", 2) == 0) {
|
||||
s_lang = LANG_ja;
|
||||
break;
|
||||
}
|
||||
if (wcsncmp(beg, L"ko", 2) == 0) {
|
||||
s_lang = LANG_ko;
|
||||
break;
|
||||
}
|
||||
if (wcsncmp(beg, L"pt-br", 5) == 0) {
|
||||
s_lang = LANG_pt_br;
|
||||
break;
|
||||
}
|
||||
if (wcsncmp(beg, L"ru", 2) == 0) {
|
||||
s_lang = LANG_ru;
|
||||
break;
|
||||
}
|
||||
if (wcsncmp(beg, L"zh-hant", 7) == 0) {
|
||||
s_lang = LANG_zh_hant;
|
||||
break;
|
||||
}
|
||||
if (wcsncmp(beg, L"zh", 2) == 0) {
|
||||
s_lang = LANG_zh_hans;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s8 loc(LocId id)
|
||||
{
|
||||
return s_lang_lut[id][s_lang];
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
typedef enum LocId
|
||||
{
|
||||
LOC_Ctrl,
|
||||
LOC_Alt,
|
||||
LOC_Shift,
|
||||
|
||||
// File menu
|
||||
LOC_File,
|
||||
LOC_File_Save,
|
||||
LOC_File_Save_As,
|
||||
LOC_File_Exit,
|
||||
|
||||
// Edit menu
|
||||
LOC_Edit,
|
||||
LOC_Edit_Undo,
|
||||
LOC_Edit_Redo,
|
||||
LOC_Edit_Cut,
|
||||
LOC_Edit_Copy,
|
||||
LOC_Edit_Paste,
|
||||
LOC_Edit_Find,
|
||||
LOC_Edit_Replace,
|
||||
|
||||
// Help menu
|
||||
LOC_Help,
|
||||
LOC_Help_About,
|
||||
|
||||
// Exit dialog
|
||||
LOC_Exit_Dialog_Title,
|
||||
LOC_Exit_Dialog_Yes,
|
||||
LOC_Exit_Dialog_No,
|
||||
|
||||
LOC_COUNT,
|
||||
} LocId;
|
||||
|
||||
void loc_init();
|
||||
s8 loc(LocId id);
|
||||
|
|
@ -0,0 +1,570 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "arena.h"
|
||||
#include "buffer.h"
|
||||
#include "input.h"
|
||||
#include "loc.h"
|
||||
#include "os.h"
|
||||
#include "ucd.h"
|
||||
|
||||
// TODO:
|
||||
// * Cut/Copy/Paste
|
||||
// * Find with ICU
|
||||
// * Line wrapping
|
||||
// * Verifying UTF8 on load
|
||||
// * Load non-UTF8 files
|
||||
// * Focus into submenus
|
||||
// * How to know that the parent menu is still open?
|
||||
// * Dialog for Save-As
|
||||
// * Perhaps as an input line in the status bar
|
||||
// * Accelerators (文件 (F))
|
||||
// * Undo/Redo batching/grouping
|
||||
// * Output diffing / compression
|
||||
// * OOM/IO error handling
|
||||
// --------------------------------------------------
|
||||
// * Replace
|
||||
// * Multi-Cursor
|
||||
// * Scrolling by dragging the track/thumb
|
||||
// * When selecting text, hug the line contents like in VS Code
|
||||
|
||||
int main(int argc, c8** argv)
|
||||
{
|
||||
os_init();
|
||||
|
||||
if (argc != 2) {
|
||||
os_write_stdout(S("Usage: edit <file>\r\n"));
|
||||
os_deinit();
|
||||
return 1;
|
||||
}
|
||||
|
||||
loc_init();
|
||||
VtParserState vt_parser_state = {};
|
||||
UiContext* ctx = ui_root_create();
|
||||
bool wants_save = false;
|
||||
bool wants_exit = false;
|
||||
|
||||
{
|
||||
ScratchArena scratch = scratch_beg(NULL);
|
||||
|
||||
s8 request = {};
|
||||
for (u32 i = 0; i < 16; ++i) {
|
||||
s8_append_fmt(scratch.arena, &request, "\033]4;", i, ";?\033\\");
|
||||
}
|
||||
s8_append(scratch.arena, &request, S("\033[c"));
|
||||
os_write_stdout(request);
|
||||
|
||||
u32 indexed_colors[16] = {
|
||||
0xff000000,
|
||||
0xff212cbe,
|
||||
0xff3aae3f,
|
||||
0xff4a9abe,
|
||||
0xffbe4d20,
|
||||
0xffbe54bb,
|
||||
0xffb2a700,
|
||||
0xffbebebe,
|
||||
0xff808080,
|
||||
0xff303eff,
|
||||
0xff51ea58,
|
||||
0xff44c9ff,
|
||||
0xffff6a2f,
|
||||
0xffff74fc,
|
||||
0xfff0e100,
|
||||
0xffffffff,
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
s8 response = os_read_stdin(scratch.arena);
|
||||
if (response.len == 0) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
c8* it = response.beg;
|
||||
c8* end = response.beg + response.len;
|
||||
|
||||
do {
|
||||
// TODO: Need to fix vt_parse_next_token to support broken up OSC sequences and distinguishing between a completed and uncompleted ones.
|
||||
it = vt_parse_next_token(&vt_parser_state, it, end);
|
||||
|
||||
if (vt_parser_state.kind == VT_TOKEN_KIND_OSC) {
|
||||
s8 osc = vt_parser_state.osc;
|
||||
// The response is in the form of `4;<color>;rgb:<r>/<g>/<b>`.
|
||||
if (s8_starts_with(osc, S("4;"))) {
|
||||
usize off = 2;
|
||||
|
||||
// Parse the <color>
|
||||
u32 color = 0;
|
||||
for (; off < osc.len && osc.beg[off] != ';'; ++off) {
|
||||
color = color * 10 + (osc.beg[off] - '0');
|
||||
}
|
||||
|
||||
if (color >= 16) {
|
||||
continue;
|
||||
}
|
||||
|
||||
osc = s8_slice(osc, off + 1, osc.len);
|
||||
if (!s8_starts_with(osc, S("rgb:"))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 rgb = 0;
|
||||
off = 4;
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
usize sep = s8_find(osc, off, '/');
|
||||
usize len = sep - off;
|
||||
|
||||
if (len == 2 || len == 4) {
|
||||
u64 val = s8_to_u64(s8_slice(osc, off, sep), 16);
|
||||
if (len == 4) {
|
||||
val = (val * 0xff + 0x80) / 0xffff;
|
||||
}
|
||||
rgb = (rgb >> 8) | (u32)(val << 16);
|
||||
}
|
||||
|
||||
off = sep + 1;
|
||||
}
|
||||
|
||||
indexed_colors[color] = rgb | 0xff000000;
|
||||
}
|
||||
} else if (vt_parser_state.kind == VT_TOKEN_KIND_CSI) {
|
||||
if (vt_parser_state.csi.final_byte == 'c') {
|
||||
goto setup_done;
|
||||
}
|
||||
}
|
||||
} while (it != end);
|
||||
}
|
||||
|
||||
setup_done:
|
||||
ui_root_setup_indexed_colors(ctx, indexed_colors);
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
TextBuffer* tb = text_buffer_create(text_buffer_allocator_default());
|
||||
text_buffer_read_file(tb, s8_from_ptr(argv[1]));
|
||||
|
||||
{
|
||||
ScratchArena scratch = scratch_beg(NULL);
|
||||
|
||||
s8 text = {};
|
||||
s8_reserve(scratch.arena, &text, tb->text_length);
|
||||
text.len = text_buffer_extract(tb, 0, tb->text_length, text.beg);
|
||||
|
||||
UcdMeasurement wrap;
|
||||
ucd_measure_forward(text, 0, (Point){}, 20, -1, &wrap);
|
||||
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
// 1049: Alternative Screen Buffer
|
||||
// I put the ASB switch in the beginning, just in case the terminal performs
|
||||
// some additional state tracking beyond the modes we enable/disable.
|
||||
// 1002: Cell Motion Mouse Tracking
|
||||
// 1006: SGR Mouse Mode
|
||||
// 2004: Bracketed Paste Mode
|
||||
os_write_stdout(S("\x1b[?1049h\x1b[?1002;1006;2004h"));
|
||||
os_inject_window_size_into_stdin();
|
||||
|
||||
while (true) {
|
||||
ScratchArena scratch = scratch_beg(NULL);
|
||||
|
||||
s8 input = os_read_stdin(scratch.arena);
|
||||
if (input.len == 0) {
|
||||
scratch_end(scratch);
|
||||
break;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
bool last_pass = input.len == 0;
|
||||
UiInput ui_input = get_next_ui_input(&vt_parser_state, &input);
|
||||
|
||||
// Windows is prone to sending broken/useless `WINDOW_BUFFER_SIZE_EVENT`s.
|
||||
// E.g. starting conhost will emit 3 in a row. Skip rendering in that case.
|
||||
if (ui_input.type == UI_INPUT_RESIZE && memcmp(&ui_input.resize, &ctx->size, sizeof(Size)) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ctx = ui_root_reset(ctx, ui_input);
|
||||
|
||||
ui_menubar_begin(ctx);
|
||||
ui_attr_background_rgba(ctx, 0x3f7f7f7f);
|
||||
ui_attr_foreground_rgba(ctx, 0xffffffff);
|
||||
{
|
||||
if (ui_menubar_menu_begin(ctx, loc(LOC_File), 'F')) {
|
||||
if (ui_menubar_menu_item(ctx, loc(LOC_File_Save), 'S', KEYBOARD_MODIFIER_CTRL | 'S')) {
|
||||
wants_save = true;
|
||||
}
|
||||
if (ui_menubar_menu_item(ctx, loc(LOC_File_Save_As), 'A', KEYBOARD_MODIFIER_CTRL | KEYBOARD_MODIFIER_SHIFT | 'S')) {
|
||||
}
|
||||
if (ui_menubar_menu_item(ctx, loc(LOC_File_Exit), 'X', KEYBOARD_MODIFIER_CTRL | 'Q')) {
|
||||
wants_exit = true;
|
||||
}
|
||||
ui_menubar_menu_end(ctx);
|
||||
}
|
||||
if (ui_menubar_menu_begin(ctx, loc(LOC_Edit), 'E')) {
|
||||
if (ui_menubar_menu_item(ctx, loc(LOC_Edit_Undo), 'U', KEYBOARD_MODIFIER_CTRL | 'Z')) {
|
||||
// TODO: This and the ones below are essentially hacks. I'm not 100% sure how to inject the input yet.
|
||||
// I could use bools and call the corresponding functions on the text buffer below, perhaps?
|
||||
ctx->input_mouse_action = MOUSE_ACTION_NONE;
|
||||
ctx->input_keyboard.key = VK_Z;
|
||||
ctx->input_keyboard.modifiers = KEYBOARD_MODIFIER_CTRL;
|
||||
ctx->input_consumed = false;
|
||||
}
|
||||
if (ui_menubar_menu_item(ctx, loc(LOC_Edit_Redo), 'R', KEYBOARD_MODIFIER_CTRL | 'Y')) {
|
||||
ctx->input_mouse_action = MOUSE_ACTION_NONE;
|
||||
ctx->input_keyboard.key = VK_Y;
|
||||
ctx->input_keyboard.modifiers = KEYBOARD_MODIFIER_CTRL;
|
||||
ctx->input_consumed = false;
|
||||
}
|
||||
if (ui_menubar_menu_item(ctx, loc(LOC_Edit_Cut), 'T', KEYBOARD_MODIFIER_CTRL | 'X')) {
|
||||
ctx->input_mouse_action = MOUSE_ACTION_NONE;
|
||||
ctx->input_keyboard.key = VK_X;
|
||||
ctx->input_keyboard.modifiers = KEYBOARD_MODIFIER_CTRL;
|
||||
ctx->input_consumed = false;
|
||||
}
|
||||
if (ui_menubar_menu_item(ctx, loc(LOC_Edit_Copy), 'C', KEYBOARD_MODIFIER_CTRL | 'C')) {
|
||||
ctx->input_mouse_action = MOUSE_ACTION_NONE;
|
||||
ctx->input_keyboard.key = VK_C;
|
||||
ctx->input_keyboard.modifiers = KEYBOARD_MODIFIER_CTRL;
|
||||
ctx->input_consumed = false;
|
||||
}
|
||||
if (ui_menubar_menu_item(ctx, loc(LOC_Edit_Paste), 'P', KEYBOARD_MODIFIER_CTRL | 'V')) {
|
||||
ctx->input_mouse_action = MOUSE_ACTION_NONE;
|
||||
ctx->input_keyboard.key = VK_V;
|
||||
ctx->input_keyboard.modifiers = KEYBOARD_MODIFIER_CTRL;
|
||||
ctx->input_consumed = false;
|
||||
}
|
||||
if (ui_menubar_menu_item(ctx, loc(LOC_Edit_Find), 'F', KEYBOARD_MODIFIER_CTRL | 'F')) {
|
||||
}
|
||||
if (ui_menubar_menu_item(ctx, loc(LOC_Edit_Replace), 'R', KEYBOARD_MODIFIER_CTRL | 'H')) {
|
||||
}
|
||||
ui_menubar_menu_end(ctx);
|
||||
}
|
||||
if (ui_menubar_menu_begin(ctx, loc(LOC_Help), 'H')) {
|
||||
if (ui_menubar_menu_item(ctx, loc(LOC_Help_About), 'A', 0)) {
|
||||
}
|
||||
ui_menubar_menu_end(ctx);
|
||||
}
|
||||
}
|
||||
ui_menubar_end(ctx);
|
||||
|
||||
ui_focus_next_by_default(ctx);
|
||||
ui_textarea(ctx, tb, (Size){0, ui_root_get_size(ctx).height - 2});
|
||||
|
||||
ui_container_begin_named(ctx, S("statusbar"));
|
||||
ui_attr_background_rgba(ctx, 0x3f7f7f7f);
|
||||
ui_attr_foreground_rgba(ctx, 0xffffffff);
|
||||
{
|
||||
s8 status = {};
|
||||
s8_append_fmt(scratch.arena, &status, "Ln ", tb->cursor.logical_pos.y + 1, ", Col ", tb->cursor.logical_pos.x + 1);
|
||||
s8_append(scratch.arena, &status, tb->overtype ? S(" OVR") : S(" INS"));
|
||||
ui_label(ctx, status);
|
||||
ui_attr_padding(ctx, (Rect){1, 0, 1, 0});
|
||||
}
|
||||
ui_container_end(ctx);
|
||||
|
||||
if (wants_save) {
|
||||
text_buffer_write_file(tb, s8_from_ptr(argv[1]));
|
||||
wants_save = false;
|
||||
}
|
||||
|
||||
if (wants_exit) {
|
||||
if (!tb->dirty) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ui_container_begin_named(ctx, S("exit"));
|
||||
ui_attr_foreground_indexed(ctx, 15);
|
||||
ui_attr_background_indexed(ctx, 1);
|
||||
ui_attr_border(ctx);
|
||||
ui_attr_float(ctx, (UiFloatSpec){.gravity_x = 0.5f, .gravity_y = 0.5f, .offset_x = ctx->size.width / 2, .offset_y = ctx->size.height / 2});
|
||||
{
|
||||
ui_label(ctx, loc(LOC_Exit_Dialog_Title));
|
||||
ui_attr_padding(ctx, (Rect){2, 0, 2, 1});
|
||||
|
||||
ui_container_begin_named(ctx, S("buttons"));
|
||||
ui_attr_grid_columns(ctx, (UiGridColumns){.count = 2, .widths = (CoordType[]){-1, -1}});
|
||||
{
|
||||
if (ui_button(ctx, loc(LOC_Exit_Dialog_Yes))) {
|
||||
goto exit;
|
||||
}
|
||||
if (ui_button(ctx, loc(LOC_Exit_Dialog_No))) {
|
||||
wants_exit = false;
|
||||
}
|
||||
}
|
||||
ui_container_end(ctx);
|
||||
}
|
||||
ui_container_end(ctx);
|
||||
}
|
||||
|
||||
if (ui_consume_shortcut(ctx, KEYBOARD_MODIFIER_CTRL | 'S')) {
|
||||
wants_save = true;
|
||||
}
|
||||
if (ui_consume_shortcut(ctx, KEYBOARD_MODIFIER_CTRL | KEYBOARD_MODIFIER_SHIFT | 'S')) {
|
||||
debug_print("Save As\n");
|
||||
}
|
||||
if (ui_consume_shortcut(ctx, KEYBOARD_MODIFIER_CTRL | 'Q')) {
|
||||
wants_exit = true;
|
||||
}
|
||||
if (ui_consume_shortcut(ctx, KEYBOARD_MODIFIER_CTRL | 'X')) {
|
||||
debug_print("Cut\n");
|
||||
}
|
||||
if (ui_consume_shortcut(ctx, KEYBOARD_MODIFIER_CTRL | 'C')) {
|
||||
debug_print("Copy\n");
|
||||
}
|
||||
if (ui_consume_shortcut(ctx, KEYBOARD_MODIFIER_CTRL | 'V')) {
|
||||
debug_print("Paste\n");
|
||||
}
|
||||
if (ui_consume_shortcut(ctx, KEYBOARD_MODIFIER_CTRL | 'F')) {
|
||||
debug_print("Find\n");
|
||||
}
|
||||
if (ui_consume_shortcut(ctx, KEYBOARD_MODIFIER_CTRL | 'H')) {
|
||||
debug_print("Replace\n");
|
||||
}
|
||||
|
||||
if (last_pass) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
s8 output = ui_root_render(ctx);
|
||||
os_write_stdout(output);
|
||||
|
||||
scratch_end(scratch);
|
||||
}
|
||||
|
||||
exit:
|
||||
// Same as in the beginning but in the reverse order.
|
||||
// It also includes DECSCUSR 0 to reset the cursor style.
|
||||
os_write_stdout(S("\x1b[?1002;1006;2004l\x1b[?1049l\x1b[0 q"));
|
||||
os_deinit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef NODEFAULTLIB
|
||||
extern int __cdecl __isa_available_init();
|
||||
__declspec(dllimport) c8* __stdcall GetCommandLineA();
|
||||
__declspec(dllimport) u32 GetModuleFileNameA(void* hModule, c8* lpFilename, u32 nSize);
|
||||
__declspec(dllimport) void __stdcall ExitProcess(u32 uExitCode);
|
||||
|
||||
static bool Parse_Cmdline(const c8* cmdstart, c8** argv, c8* lpstr, int* numargs, int* numbytes)
|
||||
{
|
||||
const c8* p;
|
||||
c8 c;
|
||||
int inquote; /* 1 = inside quotes */
|
||||
int copychar; /* 1 = copy char to *args */
|
||||
short numslash; /* num of backslashes seen */
|
||||
int nbytes = 0;
|
||||
|
||||
int argc = *numargs; /* store the value to check for buffer overrun */
|
||||
|
||||
*numargs = 1; /* the program name at least */
|
||||
|
||||
/* first scan the program name, copy it, and count the bytes */
|
||||
p = cmdstart;
|
||||
if (argv) {
|
||||
*argv++ = lpstr;
|
||||
__analysis_assume(lpstr != NULL);
|
||||
} else {
|
||||
__analysis_assume(lpstr == NULL);
|
||||
}
|
||||
|
||||
/* A quoted program name is handled here. The handling is much
|
||||
simpler than for other arguments. Basically, whatever lies
|
||||
between the leading double-quote and next one, or a terminal null
|
||||
character is simply accepted. Fancier handling is not required
|
||||
because the program name must be a legal NTFS/HPFS file name.
|
||||
Note that the double-quote characters are not copied, nor do they
|
||||
contribute to numbytes. */
|
||||
if (*p == '"') {
|
||||
/* scan from just past the first double-quote through the next
|
||||
double-quote, or up to a null, whichever comes first */
|
||||
while (*++p != '"' && *p != '\0') {
|
||||
nbytes += sizeof(c8);
|
||||
if (lpstr) {
|
||||
if (nbytes <= *numbytes) {
|
||||
*lpstr++ = *p;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* append the terminating null */
|
||||
nbytes += sizeof(c8);
|
||||
if (lpstr) {
|
||||
if (nbytes <= *numbytes) {
|
||||
*lpstr++ = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/* if we stopped on a double-quote (usual case), skip over it */
|
||||
if (*p == '"')
|
||||
p++;
|
||||
} else {
|
||||
/* Not a quoted program name */
|
||||
do {
|
||||
nbytes += sizeof(c8);
|
||||
if (lpstr) {
|
||||
if (nbytes <= *numbytes) {
|
||||
*lpstr++ = *p;
|
||||
}
|
||||
}
|
||||
|
||||
c = (c8)*p++;
|
||||
|
||||
} while (c > ' ');
|
||||
|
||||
if (c == '\0') {
|
||||
p--;
|
||||
} else {
|
||||
if (lpstr) {
|
||||
if (nbytes <= *numbytes) {
|
||||
*(lpstr - 1) = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inquote = 0;
|
||||
|
||||
/* loop on each argument */
|
||||
for (;;) {
|
||||
if (*p) {
|
||||
while (*p == ' ' || *p == '\t')
|
||||
++p;
|
||||
}
|
||||
|
||||
if (*p == '\0')
|
||||
break; /* end of args */
|
||||
|
||||
/* scan an argument */
|
||||
if (argv && *numargs < argc) {
|
||||
*argv++ = lpstr; /* store ptr to arg */
|
||||
}
|
||||
++*numargs; /* found another arg */
|
||||
|
||||
/* loop through scanning one argument */
|
||||
for (;;) {
|
||||
copychar = 1;
|
||||
/* Rules: 2N backslashes + " ==> N backslashes and begin/end quote
|
||||
2N+1 backslashes + " ==> N backslashes + literal "
|
||||
N backslashes ==> N backslashes */
|
||||
numslash = 0;
|
||||
while (*p == '\\') {
|
||||
/* count number of backslashes for use below */
|
||||
++p;
|
||||
++numslash;
|
||||
}
|
||||
if (*p == '"') {
|
||||
/* if 2N backslashes before, start/end quote, otherwise
|
||||
copy literally */
|
||||
if (numslash % 2 == 0) {
|
||||
if (inquote)
|
||||
if (p[1] == '"')
|
||||
p++; /* Double quote inside quoted string */
|
||||
else /* skip first quote char and copy second */
|
||||
copychar = 0;
|
||||
else
|
||||
copychar = 0; /* don't copy quote */
|
||||
|
||||
inquote = !inquote;
|
||||
}
|
||||
numslash /= 2; /* divide numslash by two */
|
||||
}
|
||||
|
||||
/* copy slashes */
|
||||
while (numslash--) {
|
||||
nbytes += sizeof(c8);
|
||||
if (lpstr) {
|
||||
if (nbytes <= *numbytes) {
|
||||
*lpstr++ = '\\';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* if at end of arg, break loop */
|
||||
if (*p == '\0' || (!inquote && (*p == ' ' || *p == '\t')))
|
||||
break;
|
||||
|
||||
/* copy character into argument */
|
||||
if (copychar) {
|
||||
nbytes += sizeof(c8);
|
||||
if (lpstr) {
|
||||
if (nbytes <= *numbytes) {
|
||||
*lpstr++ = *p;
|
||||
}
|
||||
}
|
||||
}
|
||||
++p;
|
||||
}
|
||||
|
||||
/* null-terminate the argument */
|
||||
|
||||
nbytes += sizeof(c8);
|
||||
if (lpstr) {
|
||||
if (nbytes <= *numbytes) {
|
||||
*lpstr++ = '\0'; /* terminate string */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lpstr) {
|
||||
return nbytes <= *numbytes && *numargs <= argc;
|
||||
} else {
|
||||
*numbytes = nbytes;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static c8** CommandLineToArgvA(Arena* arena, const c8* lpCmdLine, int* pNumArgs)
|
||||
{
|
||||
c8** argv_U = NULL;
|
||||
const c8* cmdstart; /* start of command line to parse */
|
||||
int numbytes = 0;
|
||||
c8 pgmname[260];
|
||||
|
||||
if (NULL != pNumArgs) {
|
||||
/* Get the program name pointer from Win32 Base */
|
||||
|
||||
GetModuleFileNameA(NULL, pgmname, sizeof(pgmname) / sizeof(c8));
|
||||
|
||||
/* if there's no command line at all (won't happen from cmd.exe, but
|
||||
possibly another program), then we use pgmname as the command line
|
||||
to parse, so that argv[0] is initialized to the program name */
|
||||
cmdstart = *lpCmdLine == '\0' ? pgmname : lpCmdLine;
|
||||
|
||||
/* set *pNumArgs to 1 to start with */
|
||||
*pNumArgs = 1;
|
||||
|
||||
/* first find out how much space is needed to store args */
|
||||
Parse_Cmdline(cmdstart, NULL, NULL, pNumArgs, &numbytes);
|
||||
|
||||
// 0 < ((*pNumArgs+1) * sizeof(c8*) + numbytes) <= INT_MAX
|
||||
if (0 < *pNumArgs && *pNumArgs <= (int)((INT_MAX - numbytes) / sizeof(c8*) - 1)) {
|
||||
/* allocate space for argv[] vector and strings */
|
||||
argv_U = (c8**)arena_zalloc_raw(arena, (*pNumArgs + 1) * sizeof(c8*) + numbytes, 16);
|
||||
if (argv_U) {
|
||||
/* store args and argv ptrs in just allocated block */
|
||||
bool bVal = Parse_Cmdline(cmdstart, argv_U, (c8*)argv_U + (*pNumArgs + 1) * sizeof(c8*), pNumArgs, &numbytes);
|
||||
if (!bVal) {
|
||||
argv_U = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return argv_U;
|
||||
}
|
||||
|
||||
extern void mainCRTStartup()
|
||||
{
|
||||
// __isa_available_init ensures that `extern int __isa_available` has the right value.
|
||||
// The msvcrt/vcruntime technically initializes a bunch of other things, but we need none of those.
|
||||
__isa_available_init();
|
||||
|
||||
ScratchArena scratch = scratch_beg(NULL);
|
||||
c8* command_line = GetCommandLineA();
|
||||
int argc = 0;
|
||||
c8** argv = CommandLineToArgvA(scratch.arena, command_line, &argc);
|
||||
|
||||
ExitProcess(main(argc, argv));
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "arena.h"
|
||||
|
||||
typedef u32 PlatformError;
|
||||
|
||||
void os_init();
|
||||
void os_deinit();
|
||||
void os_inject_window_size_into_stdin();
|
||||
s8 os_read_stdin(Arena* arena);
|
||||
void os_write_stdout(s8 str);
|
||||
|
||||
void* os_virtual_reserve(usize size);
|
||||
void os_virtual_release(void* base);
|
||||
void os_virtual_commit(void* base, usize size);
|
||||
|
||||
void* os_open_file_for_reading(s8 path);
|
||||
void* os_open_file_for_writing(s8 path);
|
||||
usize os_file_size(void* handle);
|
||||
void os_close_file(void* handle);
|
||||
usize os_read_file(void* handle, void* buffer, usize length);
|
||||
PlatformError os_write_file(void* handle, void* buffer, usize length);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// C4047: '=': '...' differs in levels of indirection from 'os_anon_proc'
|
||||
// C4113: 'os_anon_proc' differs in parameter lists from '...'
|
||||
// C4113: '=': incompatible types - from 'os_anon_proc' to '...'
|
||||
#define OS_SUPPRESS_GET_PROC_NAGGING_BEGIN __pragma(warning(push)) __pragma(warning(disable : 4047 4113 4133))
|
||||
#define OS_SUPPRESS_GET_PROC_NAGGING_END __pragma(warning(pop))
|
||||
#else
|
||||
#define OS_SUPPRESS_GET_PROC_NAGGING_BEGIN
|
||||
#define OS_SUPPRESS_GET_PROC_NAGGING_END
|
||||
#endif
|
||||
|
||||
typedef void (*os_anon_proc)();
|
||||
void* os_load_library(const char* name);
|
||||
os_anon_proc os_get_proc_address(void* library, const char* name);
|
||||
|
||||
inline u32 os_bit_width_u32(u32 val)
|
||||
{
|
||||
unsigned long index;
|
||||
if (!_BitScanReverse(&index, val)) {
|
||||
index = (unsigned long)-1;
|
||||
}
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
inline u32 os_bit_width_u64(u64 val)
|
||||
{
|
||||
unsigned long index;
|
||||
if (!_BitScanReverse64(&index, val)) {
|
||||
index = (unsigned long)-1;
|
||||
}
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
#define os_bit_width(val) _Generic((val), u32: os_bit_width_u32, u64: os_bit_width_u64)(val)
|
||||
|
||||
inline u32 os_bit_ceil_u32(u32 val)
|
||||
{
|
||||
return (u32)1 << os_bit_width_u32(val - 1);
|
||||
}
|
||||
|
||||
inline u64 os_bit_ceil_u64(u64 val)
|
||||
{
|
||||
return (u64)1 << os_bit_width_u64(val - 1);
|
||||
}
|
||||
|
||||
#define os_bit_ceil(val) _Generic((val), u32: os_bit_ceil_u32, u64: os_bit_ceil_u64)(val)
|
||||
|
||||
#ifndef NDEBUG
|
||||
void debug_print(const char* fmt, ...);
|
||||
#else
|
||||
#define debug_print(...) ((void)0)
|
||||
#endif
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "os.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
|
||||
#define CONSOLE_READ_NOWAIT 0x0002
|
||||
typedef BOOL(WINAPI* fn_ReadConsoleInputExW)(HANDLE hConsoleInput, PINPUT_RECORD lpBuffer, DWORD nLength, LPDWORD lpNumberOfEventsRead, USHORT wFlags);
|
||||
|
||||
static fn_ReadConsoleInputExW s_ReadConsoleInputExW;
|
||||
static HANDLE s_stdin;
|
||||
static HANDLE s_stdout;
|
||||
static DWORD s_stdin_cp_old;
|
||||
static DWORD s_stdout_cp_old;
|
||||
static DWORD s_stdin_mode_old;
|
||||
static DWORD s_stdout_mode_old;
|
||||
static bool s_inject_resize;
|
||||
static bool s_wants_exit;
|
||||
|
||||
// UTF-16 text
|
||||
typedef struct s16 {
|
||||
c16* beg;
|
||||
usize len;
|
||||
usize cap;
|
||||
} s16;
|
||||
|
||||
static s8 s16_to_s8(Arena* arena, s16 str)
|
||||
{
|
||||
s8 result = {};
|
||||
result.len = WideCharToMultiByte(CP_UTF8, 0, str.beg, (int)str.len, NULL, 0, NULL, NULL);
|
||||
result.beg = arena_mallocn(arena, c8, result.len);
|
||||
WideCharToMultiByte(CP_UTF8, 0, str.beg, (int)str.len, (LPSTR)result.beg, (int)result.len, NULL, NULL);
|
||||
return result;
|
||||
}
|
||||
|
||||
static LPWSTR s8_to_lpwstr(Arena* arena, s8 str)
|
||||
{
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, (LPSTR)str.beg, (int)str.len, NULL, 0);
|
||||
if (len <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LPWSTR beg = arena_mallocn(arena, wchar_t, len + 1);
|
||||
len = MultiByteToWideChar(CP_UTF8, 0, (LPSTR)str.beg, (int)str.len, (LPWSTR)beg, len);
|
||||
if (len <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
beg[len] = 0;
|
||||
return beg;
|
||||
}
|
||||
|
||||
static BOOL WINAPI console_ctrl_handler(DWORD ctrl_type)
|
||||
{
|
||||
s_wants_exit = true;
|
||||
CancelIoEx(s_stdin, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
void os_init()
|
||||
{
|
||||
s_ReadConsoleInputExW = (fn_ReadConsoleInputExW)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "ReadConsoleInputExW");
|
||||
|
||||
SetConsoleCtrlHandler(console_ctrl_handler, true);
|
||||
|
||||
s_stdin = GetStdHandle(STD_INPUT_HANDLE);
|
||||
s_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
s_stdin_cp_old = GetConsoleCP();
|
||||
s_stdout_cp_old = GetConsoleOutputCP();
|
||||
GetConsoleMode(s_stdin, &s_stdin_mode_old);
|
||||
GetConsoleMode(s_stdout, &s_stdout_mode_old);
|
||||
|
||||
SetConsoleCP(CP_UTF8);
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
SetConsoleMode(s_stdin, ENABLE_WINDOW_INPUT | ENABLE_EXTENDED_FLAGS | ENABLE_VIRTUAL_TERMINAL_INPUT);
|
||||
SetConsoleMode(s_stdout, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN);
|
||||
}
|
||||
|
||||
void os_deinit()
|
||||
{
|
||||
SetConsoleCP(s_stdin_cp_old);
|
||||
SetConsoleOutputCP(s_stdout_cp_old);
|
||||
SetConsoleMode(s_stdin, s_stdin_mode_old);
|
||||
SetConsoleMode(s_stdout, s_stdout_mode_old);
|
||||
}
|
||||
|
||||
void os_inject_window_size_into_stdin()
|
||||
{
|
||||
s_inject_resize = true;
|
||||
}
|
||||
|
||||
s8 os_read_stdin(Arena* arena)
|
||||
{
|
||||
s8 text = {};
|
||||
|
||||
if (s_inject_resize) {
|
||||
s_inject_resize = false;
|
||||
|
||||
CONSOLE_SCREEN_BUFFER_INFOEX info = {};
|
||||
info.cbSize = sizeof(info);
|
||||
if (!GetConsoleScreenBufferInfoEx(s_stdout, &info)) {
|
||||
return (s8){};
|
||||
}
|
||||
|
||||
int w = max(info.srWindow.Right - info.srWindow.Left + 1, 1);
|
||||
int h = max(info.srWindow.Bottom - info.srWindow.Top + 1, 1);
|
||||
s8_append_fmt(arena, &text, "\x1b[8;", h, ";", w, "t");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
INPUT_RECORD input[1024];
|
||||
DWORD read = 0;
|
||||
USHORT flags = text.len == 0 ? 0 : CONSOLE_READ_NOWAIT;
|
||||
if (!s_ReadConsoleInputExW(s_stdin, &input[0], array_size(input), &read, flags) || s_wants_exit) {
|
||||
return (s8){};
|
||||
}
|
||||
|
||||
for (DWORD i = 0; i < read; ++i) {
|
||||
switch (input[i].EventType) {
|
||||
case KEY_EVENT: {
|
||||
const KEY_EVENT_RECORD* event = &input[i].Event.KeyEvent;
|
||||
if (event->bKeyDown && event->uChar.UnicodeChar) {
|
||||
// Convert the UCS2 character to UTF8 without calling any functions.
|
||||
c8 utf8[4];
|
||||
usize utf8_len = 0;
|
||||
if (event->uChar.UnicodeChar < 0x80) {
|
||||
utf8[utf8_len++] = (c8)event->uChar.UnicodeChar;
|
||||
} else if (event->uChar.UnicodeChar < 0x800) {
|
||||
utf8[utf8_len++] = (c8)(0xC0 | (event->uChar.UnicodeChar >> 6));
|
||||
utf8[utf8_len++] = (c8)(0x80 | (event->uChar.UnicodeChar & 0x3F));
|
||||
} else {
|
||||
utf8[utf8_len++] = (c8)(0xE0 | (event->uChar.UnicodeChar >> 12));
|
||||
utf8[utf8_len++] = (c8)(0x80 | ((event->uChar.UnicodeChar >> 6) & 0x3F));
|
||||
utf8[utf8_len++] = (c8)(0x80 | (event->uChar.UnicodeChar & 0x3F));
|
||||
}
|
||||
s8_append(arena, &text, (s8){utf8, utf8_len, utf8_len});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WINDOW_BUFFER_SIZE_EVENT: {
|
||||
const WINDOW_BUFFER_SIZE_RECORD* event = &input[i].Event.WindowBufferSizeEvent;
|
||||
// If I read xterm's documentation correctly, CSI 18 t reports the window size in characters.
|
||||
// CSI 8 ; height ; width t is the response. Of course, we didn't send the request,
|
||||
// but we can use this fake response to trigger the editor to resize itself.
|
||||
int w = max((int)event->dwSize.X, 1);
|
||||
int h = max((int)event->dwSize.Y, 1);
|
||||
s8_append_fmt(arena, &text, "\x1b[8;", h, ";", w, "t");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (text.len != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
void os_write_stdout(s8 str)
|
||||
{
|
||||
DWORD written = 0;
|
||||
WriteFile(s_stdout, str.beg, (DWORD)str.len, &written, NULL);
|
||||
assert(written == str.len);
|
||||
}
|
||||
|
||||
void* os_virtual_reserve(usize size)
|
||||
{
|
||||
void* base = NULL;
|
||||
|
||||
#ifndef NDEBUG
|
||||
static uintptr_t s_base_gen;
|
||||
s_base_gen += 0x0000100000000000;
|
||||
base = (void*)s_base_gen;
|
||||
#endif
|
||||
|
||||
return VirtualAlloc(base, size, MEM_RESERVE, PAGE_READWRITE);
|
||||
}
|
||||
|
||||
void os_virtual_release(void* base)
|
||||
{
|
||||
VirtualFree(base, 0, MEM_RELEASE);
|
||||
}
|
||||
|
||||
void os_virtual_commit(void* base, usize size)
|
||||
{
|
||||
VirtualAlloc(base, size, MEM_COMMIT, PAGE_READWRITE);
|
||||
}
|
||||
|
||||
void* os_open_file_for_reading(s8 path)
|
||||
{
|
||||
ScratchArena scratch = scratch_beg(NULL);
|
||||
LPWSTR p = s8_to_lpwstr(scratch.arena, path);
|
||||
HANDLE h = CreateFileW(p, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
||||
scratch_end(scratch);
|
||||
return h;
|
||||
}
|
||||
|
||||
void* os_open_file_for_writing(s8 path)
|
||||
{
|
||||
ScratchArena scratch = scratch_beg(NULL);
|
||||
LPWSTR p = s8_to_lpwstr(scratch.arena, path);
|
||||
HANDLE h = CreateFileW(p, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
scratch_end(scratch);
|
||||
return h;
|
||||
}
|
||||
|
||||
usize os_file_size(void* handle)
|
||||
{
|
||||
LARGE_INTEGER size = {};
|
||||
GetFileSizeEx(handle, &size);
|
||||
return (usize)size.QuadPart;
|
||||
}
|
||||
|
||||
void os_close_file(void* handle)
|
||||
{
|
||||
CloseHandle(handle);
|
||||
}
|
||||
|
||||
usize os_read_file(void* handle, void* buffer, usize length)
|
||||
{
|
||||
DWORD read = 0;
|
||||
ReadFile(handle, buffer, (DWORD)length, &read, NULL);
|
||||
return read;
|
||||
}
|
||||
|
||||
static PlatformError os_get_last_error()
|
||||
{
|
||||
i32 error = (i32)GetLastError();
|
||||
if (error <= 0) {
|
||||
return error;
|
||||
}
|
||||
return 0x80070000 | (error & 0x0000ffff);
|
||||
}
|
||||
|
||||
PlatformError os_write_file(void* handle, void* buffer, usize length)
|
||||
{
|
||||
u8* bytes = buffer;
|
||||
|
||||
while (length != 0) {
|
||||
DWORD write = (DWORD)min(length, 1024 * 1024 * 1024);
|
||||
DWORD written = 0;
|
||||
|
||||
if (!WriteFile(handle, bytes, write, &written, NULL)) {
|
||||
return os_get_last_error();
|
||||
}
|
||||
|
||||
bytes += written;
|
||||
length -= written;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* os_load_library(const char* name)
|
||||
{
|
||||
return LoadLibraryExA(name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
}
|
||||
|
||||
os_anon_proc os_get_proc_address(void* library, const char* name)
|
||||
{
|
||||
return (os_anon_proc)GetProcAddress(library, name);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
void debug_print(const char* fmt, ...)
|
||||
{
|
||||
char buffer[4096];
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int len = wvsprintfA(&buffer[0], fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (len > 0 || len < sizeof(buffer)) {
|
||||
OutputDebugStringA(&buffer[0]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly
|
||||
xmlns="urn:schemas-microsoft-com:asm.v1"
|
||||
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"
|
||||
xmlns:cv1="urn:schemas-microsoft-com:compatibility.v1"
|
||||
xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings"
|
||||
xmlns:ws3="http://schemas.microsoft.com/SMI/2019/WindowsSettings"
|
||||
xmlns:ws4="http://schemas.microsoft.com/SMI/2020/WindowsSettings"
|
||||
manifestVersion="1.0">
|
||||
<asmv3:application>
|
||||
<windowsSettings>
|
||||
<ws2:longPathAware>true</ws2:longPathAware>
|
||||
<ws3:activeCodePage>UTF-8</ws3:activeCodePage>
|
||||
<ws4:heapType>SegmentHeap</ws4:heapType>
|
||||
</windowsSettings>
|
||||
</asmv3:application>
|
||||
<cv1:compatibility>
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</cv1:compatibility>
|
||||
</assembly>
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "buffer.h"
|
||||
|
||||
typedef struct UiNode UiNode;
|
||||
typedef struct UiContext UiContext;
|
||||
|
||||
typedef struct UiFloatSpec {
|
||||
// Specifies the origin of the container relative to the container size. [0, 1]
|
||||
f32 gravity_x;
|
||||
f32 gravity_y;
|
||||
// Specifies an offset from the origin in cells.
|
||||
CoordType offset_x;
|
||||
CoordType offset_y;
|
||||
} UiFloatSpec;
|
||||
|
||||
typedef struct UiGridColumns {
|
||||
usize count;
|
||||
// `data` is a pointer to an array of `CoordType` values.
|
||||
// Positive values indicate an absolute width in columns.
|
||||
// Negative values indicate a fraction of the remaining width, similar to the "fr" unit in CSS.
|
||||
CoordType* widths;
|
||||
} UiGridColumns;
|
||||
|
||||
typedef enum UiInputType {
|
||||
UI_INPUT_NONE,
|
||||
UI_INPUT_RESIZE,
|
||||
UI_INPUT_TEXT,
|
||||
UI_INPUT_KEYBOARD,
|
||||
UI_INPUT_MOUSE,
|
||||
} UiInputType;
|
||||
|
||||
typedef enum KeyboardModifier {
|
||||
KEYBOARD_MODIFIER_NONE = 0x00000000,
|
||||
KEYBOARD_MODIFIER_CTRL = 0x01000000,
|
||||
KEYBOARD_MODIFIER_ALT = 0x02000000,
|
||||
KEYBOARD_MODIFIER_SHIFT = 0x04000000,
|
||||
} KeyboardModifier;
|
||||
|
||||
typedef enum UiInputKeyboardKey {
|
||||
VK_NULL = 0x00,
|
||||
VK_BACK = 0x08,
|
||||
VK_TAB = 0x09,
|
||||
VK_RETURN = 0x0D,
|
||||
VK_ESCAPE = 0x1B,
|
||||
VK_SPACE = 0x20,
|
||||
VK_PRIOR = 0x21,
|
||||
VK_NEXT = 0x22,
|
||||
|
||||
VK_END = 0x23,
|
||||
VK_HOME = 0x24,
|
||||
|
||||
VK_LEFT = 0x25,
|
||||
VK_UP = 0x26,
|
||||
VK_RIGHT = 0x27,
|
||||
VK_DOWN = 0x28,
|
||||
|
||||
VK_INSERT = 0x2D,
|
||||
VK_DELETE = 0x2E,
|
||||
|
||||
VK_A = 'A',
|
||||
VK_B = 'B',
|
||||
VK_C = 'C',
|
||||
VK_D = 'D',
|
||||
VK_E = 'E',
|
||||
VK_F = 'F',
|
||||
VK_G = 'G',
|
||||
VK_H = 'H',
|
||||
VK_I = 'I',
|
||||
VK_J = 'J',
|
||||
VK_K = 'K',
|
||||
VK_L = 'L',
|
||||
VK_M = 'M',
|
||||
VK_N = 'N',
|
||||
VK_O = 'O',
|
||||
VK_P = 'P',
|
||||
VK_Q = 'Q',
|
||||
VK_R = 'R',
|
||||
VK_S = 'S',
|
||||
VK_T = 'T',
|
||||
VK_U = 'U',
|
||||
VK_V = 'V',
|
||||
VK_W = 'W',
|
||||
VK_X = 'X',
|
||||
VK_Y = 'Y',
|
||||
VK_Z = 'Z',
|
||||
|
||||
VK_NUMPAD0 = 0x60,
|
||||
VK_NUMPAD1 = 0x61,
|
||||
VK_NUMPAD2 = 0x62,
|
||||
VK_NUMPAD3 = 0x63,
|
||||
VK_NUMPAD4 = 0x64,
|
||||
VK_NUMPAD5 = 0x65,
|
||||
VK_NUMPAD6 = 0x66,
|
||||
VK_NUMPAD7 = 0x67,
|
||||
VK_NUMPAD8 = 0x68,
|
||||
VK_NUMPAD9 = 0x69,
|
||||
VK_MULTIPLY = 0x6A,
|
||||
VK_ADD = 0x6B,
|
||||
VK_SEPARATOR = 0x6C,
|
||||
VK_SUBTRACT = 0x6D,
|
||||
VK_DECIMAL = 0x6E,
|
||||
VK_DIVIDE = 0x6F,
|
||||
|
||||
VK_F1 = 0x70,
|
||||
VK_F2 = 0x71,
|
||||
VK_F3 = 0x72,
|
||||
VK_F4 = 0x73,
|
||||
VK_F5 = 0x74,
|
||||
VK_F6 = 0x75,
|
||||
VK_F7 = 0x76,
|
||||
VK_F8 = 0x77,
|
||||
VK_F9 = 0x78,
|
||||
VK_F10 = 0x79,
|
||||
VK_F11 = 0x7A,
|
||||
VK_F12 = 0x7B,
|
||||
VK_F13 = 0x7C,
|
||||
VK_F14 = 0x7D,
|
||||
VK_F15 = 0x7E,
|
||||
VK_F16 = 0x7F,
|
||||
VK_F17 = 0x80,
|
||||
VK_F18 = 0x81,
|
||||
VK_F19 = 0x82,
|
||||
VK_F20 = 0x83,
|
||||
VK_F21 = 0x84,
|
||||
VK_F22 = 0x85,
|
||||
VK_F23 = 0x86,
|
||||
VK_F24 = 0x87,
|
||||
} UiInputKeyboardKey;
|
||||
|
||||
typedef struct UiInputKeyboard {
|
||||
UiInputKeyboardKey key;
|
||||
KeyboardModifier modifiers;
|
||||
} UiInputKeyboard;
|
||||
|
||||
typedef enum MouseAction {
|
||||
MOUSE_ACTION_NONE,
|
||||
MOUSE_ACTION_RELEASE,
|
||||
MOUSE_ACTION_LEFT,
|
||||
MOUSE_ACTION_MIDDLE,
|
||||
MOUSE_ACTION_RIGHT,
|
||||
MOUSE_ACTION_SCROLL,
|
||||
} MouseAction;
|
||||
|
||||
typedef struct UiInputMouse {
|
||||
MouseAction action;
|
||||
KeyboardModifier modifier;
|
||||
Point position;
|
||||
Point scroll;
|
||||
} UiInputMouse;
|
||||
|
||||
typedef struct UiInput {
|
||||
UiInputType type;
|
||||
|
||||
union {
|
||||
Size resize;
|
||||
s8 text;
|
||||
UiInputKeyboard keyboard;
|
||||
UiInputMouse mouse;
|
||||
};
|
||||
} UiInput;
|
||||
|
||||
struct UiContext {
|
||||
Arena* arena;
|
||||
Arena* arena_prev;
|
||||
|
||||
u32 indexed_colors[16];
|
||||
|
||||
Size size;
|
||||
s8 input_text;
|
||||
UiInputKeyboard input_keyboard;
|
||||
MouseAction input_mouse_action;
|
||||
Point input_mouse_position;
|
||||
Point input_scroll_delta;
|
||||
bool input_consumed;
|
||||
|
||||
u64 focused_item_id;
|
||||
|
||||
UiNode* root_first;
|
||||
UiNode* root_last;
|
||||
UiNode* attr_node;
|
||||
UiNode* parent;
|
||||
usize node_count;
|
||||
bool autofocus_next;
|
||||
|
||||
UiNode** node_map;
|
||||
usize node_map_shift;
|
||||
usize node_map_mask;
|
||||
bool finalized;
|
||||
};
|
||||
|
||||
UiContext* ui_root_create();
|
||||
void ui_root_setup_indexed_colors(UiContext* ctx, u32 colors[16]);
|
||||
UiContext* ui_root_reset(UiContext* prev, UiInput input);
|
||||
s8 ui_root_render(UiContext* ctx);
|
||||
|
||||
Size ui_root_get_size(UiContext* ctx);
|
||||
|
||||
void ui_consume_input(UiContext* ctx);
|
||||
bool ui_consume_shortcut(UiContext* ctx, i32 shortcut);
|
||||
MouseAction ui_input_mouse(UiContext* ctx);
|
||||
s8 ui_input_text(UiContext* ctx);
|
||||
UiInputKeyboard ui_input_keyboard(UiContext* ctx);
|
||||
|
||||
void ui_container_begin(UiContext* ctx, u64 id);
|
||||
void ui_container_begin_named(UiContext* ctx, s8 id);
|
||||
void ui_container_end(UiContext* ctx);
|
||||
|
||||
void ui_attr_float(UiContext* ctx, UiFloatSpec spec);
|
||||
void ui_attr_border(UiContext* ctx);
|
||||
void ui_attr_padding(UiContext* ctx, Rect padding);
|
||||
void ui_attr_grid_columns(UiContext* ctx, UiGridColumns columns);
|
||||
void ui_attr_background_rgba(UiContext* ctx, u32 bg);
|
||||
void ui_attr_foreground_rgba(UiContext* ctx, u32 fg);
|
||||
void ui_attr_background_indexed(UiContext* ctx, u32 bg);
|
||||
void ui_attr_foreground_indexed(UiContext* ctx, u32 fg);
|
||||
|
||||
void ui_focus_next_by_default(UiContext* ctx);
|
||||
bool ui_is_hovering(UiContext* ctx);
|
||||
bool ui_has_focus(UiContext* ctx);
|
||||
bool ui_was_clicked(UiContext* ctx);
|
||||
|
||||
void ui_label(UiContext* ctx, s8 text);
|
||||
void ui_styled_label_begin(UiContext* ctx, s8 id);
|
||||
void ui_styled_label_set_foreground_indexed(UiContext* ctx, u32 fg);
|
||||
void ui_styled_label_add_text(UiContext* ctx, s8 text);
|
||||
void ui_styled_label_end(UiContext* ctx);
|
||||
bool ui_button(UiContext* ctx, s8 text);
|
||||
bool ui_editline(UiContext* ctx, s8 name);
|
||||
void ui_textarea(UiContext* ctx, TextBuffer* tb, Size intrinsic_size);
|
||||
|
||||
void ui_scrollarea_begin(UiContext* ctx, s8 name, Size intrinsic_size);
|
||||
void ui_scrollarea_end(UiContext* ctx);
|
||||
|
||||
void ui_menubar_begin(UiContext* ctx);
|
||||
bool ui_menubar_menu_begin(UiContext* ctx, s8 text, c8 accelerator);
|
||||
bool ui_menubar_menu_item(UiContext* ctx, s8 text, c8 accelerator, i32 shortcut);
|
||||
void ui_menubar_menu_end(UiContext* ctx);
|
||||
void ui_menubar_end(UiContext* ctx);
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
typedef struct UcdMeasurement {
|
||||
usize offset;
|
||||
Point pos;
|
||||
CoordType movements;
|
||||
bool newline;
|
||||
} UcdMeasurement;
|
||||
|
||||
UcdMeasurement ucd_measure_forward(s8 str, usize offset, Point pos, CoordType column_stop, CoordType cursor_movement_limit, UcdMeasurement* line_break);
|
||||
// ucd_measure_backward returns a negative column if it has crossed a newline as it cannot
|
||||
// possibly know what column its now in without iterating all the way to the start of the line.
|
||||
// Doing so is the job of the caller as that depends on the way the text is stored
|
||||
// (a rope for instance will have potentially many segments to iterate through).
|
||||
UcdMeasurement ucd_measure_backward(s8 str, usize offset, Point pos, CoordType column_stop, CoordType cursor_movement_limit);
|
||||
|
||||
usize ucd_newlines_forward(s8 str, usize offset, CoordType* line, CoordType line_stop);
|
||||
usize ucd_newlines_backward(s8 str, usize offset, CoordType* line, CoordType line_stop);
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#include "vt.h"
|
||||
|
||||
c8* vt_parse_next_token(VtParserState* state, c8* const beg, c8* const end)
|
||||
{
|
||||
c8* it = beg;
|
||||
|
||||
state->kind = VT_TOKEN_KIND_PENDING;
|
||||
|
||||
while (it != end) {
|
||||
switch (state->_state) {
|
||||
case VT_PARSER_STATE_KIND_GROUND:
|
||||
if (*it == '\x1b') {
|
||||
state->_state = VT_PARSER_STATE_KIND_ESC;
|
||||
it += 1;
|
||||
break;
|
||||
}
|
||||
if (*it < '\x20' || *it == '\x7f') {
|
||||
state->kind = VT_TOKEN_KIND_CTRL;
|
||||
state->ctrl = *it++;
|
||||
} else {
|
||||
state->kind = VT_TOKEN_KIND_TEXT;
|
||||
state->text.beg = it;
|
||||
for (; it != end && (*it >= '\x20' && *it != '\x7f'); it += 1) {
|
||||
}
|
||||
state->text.len = it - state->text.beg;
|
||||
}
|
||||
return it;
|
||||
case VT_PARSER_STATE_KIND_ESC: {
|
||||
c8 c = *it++;
|
||||
switch (c) {
|
||||
case '[':
|
||||
state->_state = VT_PARSER_STATE_KIND_CSI;
|
||||
state->csi.private_byte = 0;
|
||||
state->csi.final_byte = 0;
|
||||
while (state->csi.param_count > 0) {
|
||||
state->csi.param_count -= 1;
|
||||
state->csi.params[state->csi.param_count] = 0;
|
||||
}
|
||||
break;
|
||||
case ']':
|
||||
state->_state = VT_PARSER_STATE_KIND_OSC;
|
||||
state->osc.beg = it;
|
||||
state->osc.len = 0;
|
||||
break;
|
||||
case 'O':
|
||||
state->_state = VT_PARSER_STATE_KIND_SS3;
|
||||
break;
|
||||
case 'P':
|
||||
state->_state = VT_PARSER_STATE_KIND_DCS;
|
||||
state->dcs.beg = it;
|
||||
state->dcs.len = 0;
|
||||
break;
|
||||
default:
|
||||
state->_state = VT_PARSER_STATE_KIND_GROUND;
|
||||
state->kind = VT_TOKEN_KIND_ESC;
|
||||
state->esc = c;
|
||||
return it;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VT_PARSER_STATE_KIND_SS3:
|
||||
state->_state = VT_PARSER_STATE_KIND_GROUND;
|
||||
state->kind = VT_TOKEN_KIND_SS3;
|
||||
state->ss3 = *it++;
|
||||
return it;
|
||||
case VT_PARSER_STATE_KIND_CSI:
|
||||
for (;;) {
|
||||
// If we still have slots left, parse the parameter.
|
||||
if (state->csi.param_count < array_size(state->csi.params)) {
|
||||
i32* dst = &state->csi.params[state->csi.param_count];
|
||||
while (it != end && *it >= '0' && *it <= '9') {
|
||||
i32 v = *dst * 10 + (*it - '0');
|
||||
*dst = min(v, 0xffff);
|
||||
it += 1;
|
||||
}
|
||||
} else {
|
||||
// ...otherwise, skip the parameters until we find the final byte.
|
||||
while (it != end && *it >= '0' && *it <= '9') {
|
||||
it += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Encountered the end of the input before finding the final byte.
|
||||
if (it == end) {
|
||||
return it;
|
||||
}
|
||||
|
||||
c8 c = *it++;
|
||||
if (c >= '\x40' && c <= '\x7e') {
|
||||
state->_state = VT_PARSER_STATE_KIND_GROUND;
|
||||
state->kind = VT_TOKEN_KIND_CSI;
|
||||
state->csi.final_byte = c;
|
||||
state->csi.param_count += 1;
|
||||
return it;
|
||||
}
|
||||
if (c == ';') {
|
||||
state->csi.param_count += 1;
|
||||
}
|
||||
if (c >= '<' && c <= '?') {
|
||||
state->csi.private_byte = c;
|
||||
}
|
||||
}
|
||||
case VT_PARSER_STATE_KIND_OSC:
|
||||
case VT_PARSER_STATE_KIND_DCS:
|
||||
for (;;) {
|
||||
// Tight loop to find any indication for the end of the OSC/DCS sequence.
|
||||
for (; it != end && *it != '\a' && *it != '\x1b'; it += 1) {
|
||||
}
|
||||
|
||||
state->kind = state->_state == VT_PARSER_STATE_KIND_OSC ? VT_TOKEN_KIND_OSC : VT_TOKEN_KIND_DCS;
|
||||
// .osc and .dsc share the same address.
|
||||
state->osc.len = it - state->osc.beg;
|
||||
|
||||
// Encountered the end of the input before finding the terminator.
|
||||
if (it == end) {
|
||||
return it;
|
||||
}
|
||||
|
||||
c8 c = *it++;
|
||||
if (c == '\x1b') {
|
||||
// It's only a string terminator if it's followed by \.
|
||||
// We're at the end so we're saving the state and will continue next time.
|
||||
if (it == end) {
|
||||
state->_state = state->_state == VT_PARSER_STATE_KIND_OSC ? VT_PARSER_STATE_KIND_OSC_ESC : VT_PARSER_STATE_KIND_DCS_ESC;
|
||||
return it;
|
||||
}
|
||||
// False alarm: Not a string terminator.
|
||||
if (*it != '\\') {
|
||||
continue;
|
||||
}
|
||||
it += 1;
|
||||
}
|
||||
|
||||
state->_state = VT_PARSER_STATE_KIND_GROUND;
|
||||
return it;
|
||||
}
|
||||
case VT_PARSER_STATE_KIND_OSC_ESC:
|
||||
case VT_PARSER_STATE_KIND_DCS_ESC: {
|
||||
if (*it == '\\') {
|
||||
it += 1;
|
||||
state->kind = state->_state == VT_PARSER_STATE_KIND_OSC_ESC ? VT_TOKEN_KIND_OSC : VT_TOKEN_KIND_DCS;
|
||||
state->osc.len = it - state->osc.beg;
|
||||
return it;
|
||||
}
|
||||
state->_state = state->_state == VT_PARSER_STATE_KIND_OSC_ESC ? VT_PARSER_STATE_KIND_OSC : VT_PARSER_STATE_KIND_DCS;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
typedef enum VtParserStateKind {
|
||||
VT_PARSER_STATE_KIND_GROUND,
|
||||
VT_PARSER_STATE_KIND_ESC,
|
||||
VT_PARSER_STATE_KIND_SS3,
|
||||
VT_PARSER_STATE_KIND_CSI,
|
||||
VT_PARSER_STATE_KIND_OSC,
|
||||
VT_PARSER_STATE_KIND_DCS,
|
||||
VT_PARSER_STATE_KIND_OSC_ESC,
|
||||
VT_PARSER_STATE_KIND_DCS_ESC,
|
||||
} VtParserStateKind;
|
||||
|
||||
typedef enum VtTokenType {
|
||||
VT_TOKEN_KIND_TEXT,
|
||||
VT_TOKEN_KIND_CTRL,
|
||||
VT_TOKEN_KIND_ESC,
|
||||
VT_TOKEN_KIND_SS3,
|
||||
VT_TOKEN_KIND_CSI,
|
||||
VT_TOKEN_KIND_OSC,
|
||||
VT_TOKEN_KIND_DCS,
|
||||
VT_TOKEN_KIND_PENDING,
|
||||
} VtTokenKind;
|
||||
|
||||
typedef struct CsiState {
|
||||
i32 params[32];
|
||||
i32 param_count;
|
||||
c8 private_byte;
|
||||
c8 final_byte;
|
||||
} CsiState;
|
||||
|
||||
typedef struct VtParserState {
|
||||
VtParserStateKind _state; // Stores the internal state of the tokenizer.
|
||||
VtTokenKind kind; // Tells you which one of the union members you need to look at.
|
||||
|
||||
s8 text; // The plain text as a string.
|
||||
c8 ctrl; // The single control character.
|
||||
c8 esc; // A character that was prefixed by an ESC character.
|
||||
c8 ss3; // The DCS contents as a string.
|
||||
CsiState csi; // The CSI parameters and final byte.
|
||||
s8 osc; // The OSC contents as a string.
|
||||
s8 dcs; // The DCS contents as a string.
|
||||
} VtParserState;
|
||||
|
||||
c8* vt_parse_next_token(VtParserState* state, c8* const beg, c8* const end);
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
<!-- https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2017/debugger/create-custom-views-of-native-objects -->
|
||||
<Type Name="s8">
|
||||
<DisplayString>{beg,[len]}</DisplayString>
|
||||
<StringView>beg,[len]</StringView>
|
||||
<Expand>
|
||||
<Item Name="[size]" ExcludeView="simple">len</Item>
|
||||
<ArrayItems>
|
||||
<Size>len</Size>
|
||||
<ValuePointer>beg</ValuePointer>
|
||||
</ArrayItems>
|
||||
</Expand>
|
||||
</Type>
|
||||
</AutoVisualizer>
|
||||
Loading…
Reference in New Issue