752 lines
28 KiB
C
752 lines
28 KiB
C
typedef __SIZE_TYPE__ size_t;
|
|
typedef __PTRDIFF_TYPE__ ssize_t;
|
|
typedef __WCHAR_TYPE__ wchar_t;
|
|
typedef __PTRDIFF_TYPE__ ptrdiff_t;
|
|
typedef __PTRDIFF_TYPE__ intptr_t;
|
|
typedef __SIZE_TYPE__ uintptr_t;
|
|
|
|
#if __STDC_VERSION__ >= 201112L
|
|
typedef union { long long __ll; long double __ld; } max_align_t;
|
|
#endif
|
|
|
|
#ifndef NULL
|
|
#define NULL ((void*)0)
|
|
#endif
|
|
|
|
#undef offsetof
|
|
#define offsetof(type, field) __builtin_offsetof(type, field)
|
|
|
|
typedef signed char int8_t;
|
|
typedef unsigned char uint8_t;
|
|
typedef short int16_t;
|
|
typedef unsigned short uint16_t;
|
|
typedef int int32_t;
|
|
typedef unsigned uint32_t;
|
|
typedef long long int64_t;
|
|
typedef unsigned long long uint64_t;
|
|
|
|
typedef __builtin_va_list va_list;
|
|
#define va_start __builtin_va_start
|
|
#define va_arg __builtin_va_arg
|
|
#define va_copy __builtin_va_copy
|
|
#define va_end __builtin_va_end
|
|
|
|
/* fix a buggy dependency on GCC in libio.h */
|
|
typedef va_list __gnuc_va_list;
|
|
#define _VA_LIST_DEFINED
|
|
|
|
// Basic CRT protos.
|
|
int memcmp(const void* _Buf1, const void* _Buf2, size_t _Size);
|
|
void* memcpy(void* _Dst, const void* _Src, size_t _Size);
|
|
void* memset(void* _Dst, int _Val, size_t _Size);
|
|
|
|
typedef enum EditorCommands {
|
|
ED_SaveRequestSave, // (.cmd) Saves the current buffer if there are changes.
|
|
ED_SaveRequest_END = 0xF,
|
|
ED_CloseRequestClose, // (.cmd) Closes the current editor.
|
|
ED_CloseRequest_END = 0x1F,
|
|
ED_RequestClipboardCopy, // (.cmd) Copies selection to clipboard. If no selection it will copy the content line.
|
|
ED_RequestClipboardCut, // (.cmd) Copies with the same semantics as copy to clipboard, but also removes the content.
|
|
ED_RequestClipboardPaste, // (.cmd) Inserts the content of the system clipboard into the current buffer.
|
|
ED_RequestClipboardPasteBeforeCursor, // (.cmd) Inserts the content of the system clipboard into the buffer. If it is a line copy, the line is pasted before the cursor line.
|
|
ED_RequestClipboard_END = 0x2F,
|
|
ED_FindRequestFind, // (.cmd) Opens the 'Find' widget.
|
|
ED_FindRequestFindWithSeed, // (.cmd) Opens the 'Find' widget but seeds the search with the current selection or word under cursor.
|
|
ED_FindRequest_END = 0x3F,
|
|
ED_SelectionClearSelections, // (.cmd) Clears all selections.
|
|
ED_Selection_END = 0x4F,
|
|
ED_NavPageUp, // (.cmd,.flags) Moves cursor up by one page.
|
|
ED_NavPageDown, // (.cmd,.flags) Moves cursor down by one page.
|
|
ED_NavLeft, // (.cmd,.flags) Moves cursor to the left. If at the beginning of the line, cursor will advance to end of line above.
|
|
ED_NavRight, // (.cmd,.flags) Moves cursor to the right. If at the end of the line, cursor will advance to the beginning of the line below.
|
|
ED_NavRightNoNLAdvance, // (.cmd,.flags) Moves cursor to the right.
|
|
ED_NavLineDown, // (.cmd,.flags) Moves cursor down a line maintaining column.
|
|
ED_NavLineUp, // (.cmd,.flags) Moves cursor up a line maintaining column.
|
|
ED_NavCursorTopScreen, // (.cmd,.flags) Moves cursor to top of viewport.
|
|
ED_NavCursorBottomScreen, // (.cmd,.flags) Moves cursor to bottom of viewport.
|
|
ED_NavCursorCenterScreen, // (.cmd,.flags) Moves cursor to middle of viewport.
|
|
ED_NavBeginningOfLine, // (.cmd,.flags) Moves cursor to beginning of the line.
|
|
ED_NavFirstNonemptyOfLine, // (.cmd,.flags) Moves cursor to first non-whitespace character of line.
|
|
ED_NavEndOfLine, // (.cmd,.flags) Moves cursor to end of the line.
|
|
ED_NavContentEnd, // (.cmd,.flags) Moves cursor to the end of the content.
|
|
ED_NavContentBeginning, // (.cmd,.flags) Moves cursor to beginning of content.
|
|
ED_NavMatchingEncloser, // (.cmd,.flags) Snaps cursor to nearest enclosing symbol. If at an enclosing symbol, cursor will move to opposing enclosing symbol.
|
|
ED_NavWordRight, // (.cmd,.flags) Moves cursor one word to the right.
|
|
ED_NavChunkRight, // (.cmd,.flags) Moves cursor one word chunk to the right.
|
|
ED_NavWordLeft, // (.cmd,.flags) Moves cursor one word to the left.
|
|
ED_NavChunkLeft, // (.cmd,.flags) Moves cursor one word chunk to the left.
|
|
ED_NavRequestGotoLine, // (.cmd,.flags) Opens go to line widget.
|
|
ED_NavCenterCameraCursor, // (.cmd,.flags) Moves the camera view such that the primary cursor is in mid screen.
|
|
ED_NavEmptyBlockUp, // (.cmd,.flags) Moves cursor to nearest empty line above.
|
|
ED_NavEmptyBlockDown, // (.cmd,.flags) Moves cursor to nearest empty line below.
|
|
ED_NavCursorHistoryBack, // (.cmd,.flags) Moves cursor to its previous position.
|
|
ED_NavCursorHistoryForward, // (.cmd,.flags) Moves cursor to its next future position.
|
|
ED_NavMoveCursorTo, // (.cmd,.flags,.byte_offsets) Moves cursor to a specific byte position in the buffer.
|
|
ED_Nav_END = 0x4FF,
|
|
ED_MCDupCursorDown, // (.cmd) Creates a new multi-cursor one line below.
|
|
ED_MCDupCursorUp, // (.cmd) Creates a new multi-cursor one line above.
|
|
ED_MCInsertGroup, // (.cmd,.buffers) Performs a unique insert per-cursor.
|
|
ED_MCDropCursors, // (.cmd) Deletes all multi-cursors.
|
|
ED_MCSelectionToCursor, // (.cmd) Creates a selection at the current word. If selection exists, creates a new multi-cursor at the next identical selection.
|
|
ED_MCCreateCursors, // (.cmd,.byte_offsets) Creates multi-cursors at the specified byte positions.
|
|
ED_MC_END = 0x50F,
|
|
ED_InsInsert, // (.cmd,.buf) Inserts a buffer at the cursor.
|
|
ED_InsReplace, // (.cmd,.buf) Inserts a buffer at cursor, overwriting characters overlapping the buffer. Does not advance cursor.
|
|
ED_InsOverwriteInsertBuf, // (.cmd,.buf) Inserts a buffer at cursor, overwriting characters overlapping the buffer.
|
|
ED_InsOpenLineBelow, // (.cmd) Opens a new content line below cursor.
|
|
ED_InsOpenLineAbove, // (.cmd) Opens a new content line above cursor.
|
|
ED_InsTab, // (.cmd) Inserts a tap (may be expanded to spaces).
|
|
ED_Ins_END = 0x90F,
|
|
ED_SpecJoinLineBelow, // (.cmd) Joins the content line below with the current line at cursor.
|
|
ED_SpecTrimLineEndings, // (.cmd) Trims trailing spaces at all lines.
|
|
ED_SpecTab, // (.cmd) Tabs a line.
|
|
ED_SpecUntab, // (.cmd) Untabs a line.
|
|
ED_Spec_END = 0xA0F,
|
|
ED_DelDeleteLine, // (.cmd) Deletes the line of content at cursor.
|
|
ED_DelDeleteChar, // (.cmd) Deletes a single character at the cursor. If a selection is active, the selection is deleted.
|
|
ED_DelDeleteWord, // (.cmd) Deletes a word at the cursor. If a selection is active, the selection is deleted.
|
|
ED_DelDeleteChunk, // (.cmd) Deletes a word chunk at cursor. If a selection is active, the selection is deleted.
|
|
ED_DelBackspaceWord, // (.cmd) Deletes a word before the cursor. If a selection is active, the selection is deleted.
|
|
ED_DelBackspaceChunk, // (.cmd) Deletes a word chunk before the cursor. If a selection is active, the selection is deleted.
|
|
ED_Del_END = 0xF0F,
|
|
ED_URTryUndo, // (.cmd) Attemps an undo operation.
|
|
ED_URTryRedo, // (.cmd) Attemps a redo operation.
|
|
ED_UR_END = 0xF1F,
|
|
} EditorCommands;
|
|
|
|
typedef enum EditorCommandFlags {
|
|
ED_FLG_None = 0, // No options.
|
|
ED_FLG_UpdateSelection = 1 << 0, // If the cursor moves, add to the selection.
|
|
ED_FLG_ResetCamera = 1 << 1, // Make the camera follow the cursor after command.
|
|
} EditorCommandFlags;
|
|
|
|
// --- fred plugin API ---
|
|
// --- Infra structures ---
|
|
// Core string struct.
|
|
typedef struct String8 {
|
|
// String pointer (null termination not necessary).
|
|
char* str;
|
|
// Size of string (not including any null terminator).
|
|
uint64_t size;
|
|
} String8;
|
|
|
|
// Collection of strings.
|
|
typedef struct String8Array {
|
|
// Pointer to strings.
|
|
String8* strs;
|
|
// Size of array.
|
|
uint64_t size;
|
|
} String8Array;
|
|
|
|
// Structure storing a collection of offsets into the buffer.
|
|
typedef struct EditorOffsetArray {
|
|
// Pointer to offsets.
|
|
uint64_t* array;
|
|
// Size of array.
|
|
uint64_t size;
|
|
} EditorOffsetArray;
|
|
|
|
// Structure storing a pair of buffer offset ranges.
|
|
typedef struct EditorOffsetRange {
|
|
// Offset to the first byte.
|
|
uint64_t first_off;
|
|
// Last byte offset of the range (not inclusive).
|
|
uint64_t last_off;
|
|
} EditorOffsetRange;
|
|
|
|
// Structure storing cursor byte offsets.
|
|
typedef struct EditorCursorRange {
|
|
// Byte offset of the cursor.
|
|
uint64_t cursor_off;
|
|
// Will be non-empty if there is an active selection.
|
|
EditorOffsetRange sel;
|
|
} EditorCursorRange;
|
|
|
|
// Structure storing a collection of cursor ranges.
|
|
typedef struct EditorCursorArray {
|
|
// Pointer to cursor ranges.
|
|
EditorCursorRange* array;
|
|
// Size of array.
|
|
uint64_t size;
|
|
} EditorCursorArray;
|
|
|
|
// Structure storing a collection of search result ranges.
|
|
typedef struct EditorFindResults {
|
|
// Pointer to search result ranges.
|
|
EditorOffsetRange* array;
|
|
// Size of array.
|
|
uint64_t size;
|
|
} EditorFindResults;
|
|
|
|
// --- Editor batching API ---
|
|
// Structure for working with batch edits.
|
|
typedef struct EditorBatchEdit {
|
|
// Private data.
|
|
void* pvt[2];
|
|
} EditorBatchEdit;
|
|
|
|
// Structure storing a collection of batch offset and string buffer.
|
|
typedef struct EditorInsertData {
|
|
// Offset to the byte to insert at.
|
|
uint64_t off;
|
|
// Buffer to insert.
|
|
String8 buf;
|
|
} EditorInsertData;
|
|
|
|
// Structure storing a collection of batch offset and string buffer.
|
|
typedef struct EditorReplaceData {
|
|
// Range of bytes to remove and replace with 'buf'.
|
|
EditorOffsetRange range;
|
|
// Buffer to replace at 'range'.
|
|
String8 buf;
|
|
} EditorReplaceData;
|
|
|
|
// Structure storing a collection of batch insertions.
|
|
typedef struct EditorBatchInsert {
|
|
// Pointer to insert operations.
|
|
EditorInsertData* array;
|
|
// Size of array.
|
|
uint64_t size;
|
|
} EditorBatchInsert;
|
|
|
|
// Structure storing a collection of batch removals.
|
|
typedef struct EditorBatchRemove {
|
|
// Pointer to removal operations.
|
|
EditorOffsetRange* array;
|
|
// Size of array.
|
|
uint64_t size;
|
|
} EditorBatchRemove;
|
|
|
|
// Structure storing a collection of batch replacements.
|
|
typedef struct EditorBatchReplace {
|
|
// Pointer to replacement operations.
|
|
EditorReplaceData* array;
|
|
// Size of array.
|
|
uint64_t size;
|
|
} EditorBatchReplace;
|
|
|
|
// --- Context structures ---
|
|
// Opaque: Pointer to an allocating arena.
|
|
typedef struct Arena Arena;
|
|
// Opaque: Pointer to plugin manager instance.
|
|
typedef struct PluginManager PluginManager;
|
|
// Opaque: Pointer to editor instance.
|
|
typedef struct Editor Editor;
|
|
// Context structure for editor interactions.
|
|
typedef struct EditorCtx {
|
|
// Plugin manager.
|
|
PluginManager* mgr;
|
|
// Calling editor.
|
|
Editor* editor;
|
|
} EditorCtx;
|
|
|
|
// --- Command structures ---
|
|
// Structure for queuing editor commands.
|
|
typedef struct EditorCmd {
|
|
// Command.
|
|
uint32_t cmd;
|
|
// Flags applied to command.
|
|
uint32_t flags;
|
|
// Buffer for command.
|
|
String8 buf;
|
|
// Buffer collection for bulk operations.
|
|
String8Array buffers;
|
|
// Byte offsets into the buffer.
|
|
EditorOffsetArray byte_offsets;
|
|
} EditorCmd;
|
|
|
|
// --- Arenas ---
|
|
// Fetches a scratch arena that does not conflict with 'conflict'.
|
|
Arena* arena_pull_scratch(PluginManager* mgr, Arena* conflict);
|
|
// Bumps arena pointer, allocating space for the caller.
|
|
void* arena_push(Arena* a, uint64_t size, uint64_t align, uint32_t zero);
|
|
// Gets the arena position.
|
|
uint64_t arena_pos(Arena* a);
|
|
// Pops arena to specific position.
|
|
void arena_pop_to(Arena* a, uint64_t pos);
|
|
|
|
// --- Message feed ---
|
|
// Emit an info message.
|
|
void feed_queue_info_internal(String8* str);
|
|
// Emit an warning message.
|
|
void feed_queue_warning_internal(String8* str);
|
|
// Emit an error message.
|
|
void feed_queue_error_internal(String8* str);
|
|
|
|
// --- Editor ---
|
|
// Queries.
|
|
// Populates each cursor selection (empty or not) to the 'result' array.
|
|
void ed_cursor_selections(Arena* a, EditorCtx* ctx, String8Array* result);
|
|
// Gets the cursors in the current editor.
|
|
void ed_cursor_ranges(Arena* a, EditorCtx* ctx, EditorCursorArray* result);
|
|
// Gets a string at the specified byte range.
|
|
void ed_string_at_range(Arena* a, EditorCtx* ctx, EditorOffsetRange* rng, String8* result);
|
|
// Gets the line at a given byte offset in the buffer.
|
|
uint64_t ed_line_at_offset(EditorCtx* ctx, uint64_t off);
|
|
// Gets the byte range for a specific line. Note: Lines are 1-indexed.
|
|
void ed_byte_range_at_line(EditorCtx* ctx, uint64_t line, EditorOffsetRange* result);
|
|
// Gets the byte range for a specific line including the newline character. Note: Lines are 1-indexed.
|
|
void ed_byte_range_at_line_with_newline(EditorCtx* ctx, uint64_t line, EditorOffsetRange* result);
|
|
// Gets the number of lines for the buffer.
|
|
uint64_t ed_line_count(EditorCtx* ctx);
|
|
// Gets the total content length for the buffer.
|
|
uint64_t ed_content_byte_count(EditorCtx* ctx);
|
|
// Performs an immediate incremental search for 'str' over the current buffer.
|
|
void ed_find_all(Arena* a, EditorCtx* ctx, String8* str, uint32_t ignore_case, EditorFindResults* result);
|
|
// Batching.
|
|
// Starts a new batch edit. WARNING: You must not queue new commands until the 'ed_edit_batch_end' is called.
|
|
void ed_edit_batch_begin(EditorCtx* ctx, EditorBatchEdit* batch);
|
|
// Completes the batch and recomputes internal editor state.
|
|
void ed_edit_batch_end(EditorCtx* ctx, EditorBatchEdit* batch);
|
|
// Performs a batch insert. After calling this, you must recompute content length for subsequent batch edits.
|
|
void ed_edit_batch_insert(EditorBatchEdit* batch, EditorBatchInsert* ops);
|
|
// Performs a batch remove. After calling this, you must recompute content length for subsequent batch edits.
|
|
void ed_edit_batch_remove(EditorBatchEdit* batch, EditorBatchRemove* ops);
|
|
// Performs a batch range replacement. After calling this, you must recompute content length for subsequent batch edits.
|
|
void ed_edit_batch_replace(EditorBatchEdit* batch, EditorBatchReplace* ops);
|
|
// Commands.
|
|
// Pushes a new editor command.
|
|
void ed_push_command(EditorCtx* ctx, EditorCmd* cmd);
|
|
|
|
// --- Utility ---
|
|
// Formats a string printf-style and allocates result to specified arena. There is a unique specifier '%S' for String8.
|
|
void str8_fmt_internal(Arena* a, String8* result, const char* fmt, va_list vlst);
|
|
// Performs a lexicographic comparison on input strings.
|
|
int32_t str8_compare_internal(String8* a, String8* b);
|
|
// Returns 1 if the input string is an integral value of the specified radix.
|
|
uint32_t str8_is_integer_internal(String8* str, uint32_t radix);
|
|
// Converts the input string to an integral value of the specified radix.
|
|
uint64_t u64_from_str8_internal(String8* str, uint32_t radix);
|
|
// Tries to convert the intput string to a floating-point value. Returns 0 if the conversion failed.
|
|
uint32_t try_f64_from_str8_internal(String8* str, double* result);
|
|
|
|
// --- fred API version info ---
|
|
// The version of this fred API. If there is a mismatch, you should regenerate the API.
|
|
const uint32_t api_version = 1;
|
|
|
|
typedef struct Temp {
|
|
void* arena;
|
|
uint64_t pos;
|
|
} Temp;
|
|
|
|
Temp arena_temp_begin(void* arena) {
|
|
uint64_t pos = arena_pos(arena);
|
|
Temp t = { .arena = arena, .pos = pos };
|
|
return t;
|
|
}
|
|
|
|
void arena_temp_end(Temp t) {
|
|
arena_pop_to(t.arena, t.pos);
|
|
}
|
|
|
|
// Helpers for walking.
|
|
#define EachIndex(it, count) (uint64_t it = 0; it < (count); it += 1)
|
|
#define EachLine(it, count) (uint64_t it = 1; it <= (count); it += 1)
|
|
|
|
#define FRED_Min(A, B) (((A)<(B))?(A):(B))
|
|
#define FRED_Max(A,B) (((A)>(B))?(A):(B))
|
|
#define FRED_AlignOf(T) __alignof(T)
|
|
#define push_array_no_zero_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align), (0))
|
|
#define push_array_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align), (1))
|
|
#define push_array_no_zero(a, T, c) push_array_no_zero_aligned(a, T, c, FRED_Max(8, FRED_AlignOf(T)))
|
|
|
|
#define push_array(a, T, c) push_array_aligned(a, T, c, FRED_Max(8, FRED_AlignOf(T)))
|
|
|
|
// Scratch arena creation. You pass in a 'conflict' arena when you don't want one scratch arena to overwrite another when nested functions are involved.
|
|
#define scratch_begin(conflict) arena_temp_begin(arena_pull_scratch(ctx->mgr, (conflict)))
|
|
#define scratch_end(scratch) arena_temp_end(scratch)
|
|
|
|
// Feed.
|
|
void feed_queue_info(String8 msg) {
|
|
feed_queue_info_internal(&msg);
|
|
}
|
|
|
|
void feed_queue_warning(String8 msg) {
|
|
feed_queue_warning_internal(&msg);
|
|
}
|
|
|
|
void feed_queue_error(String8 msg) {
|
|
feed_queue_error_internal(&msg);
|
|
}
|
|
|
|
// Strings.
|
|
typedef struct String8Slice {
|
|
uint64_t off;
|
|
uint64_t len;
|
|
} String8Slice;
|
|
|
|
String8 str8(char* s, uint64_t size) {
|
|
String8 r = { .str = s, .size = size };
|
|
return r;
|
|
}
|
|
|
|
#define str8_lit(S) str8((char*)(S), sizeof(S) - 1)
|
|
|
|
String8 str8_substr_impl(String8 str, String8Slice slice) {
|
|
slice.off = FRED_Min(str.size, slice.off);
|
|
slice.len = FRED_Min(str.size - slice.off, slice.len);
|
|
str.str += slice.off;
|
|
str.size = slice.len;
|
|
return str;
|
|
}
|
|
|
|
#define str8_substr(str, ...) str8_substr_impl(str, (String8Slice){ .off = 0, .len = (uint64_t)-1, __VA_ARGS__ })
|
|
|
|
String8 str8_fmt(void* a, const char* fmt, ...) {
|
|
String8 result = {0};
|
|
va_list va;
|
|
va_start(va, fmt);
|
|
str8_fmt_internal(a, &result, fmt, va);
|
|
va_end(va);
|
|
return result;
|
|
}
|
|
|
|
int32_t str8_compare(String8 a, String8 b) {
|
|
return str8_compare_internal(&a, &b);
|
|
}
|
|
|
|
String8 str8_copy(void* a, String8 str) {
|
|
String8 cpy = {0};
|
|
cpy.size = str.size;
|
|
cpy.str = push_array_no_zero(a, char, str.size);
|
|
memcpy(cpy.str, str.str, str.size);
|
|
return cpy;
|
|
}
|
|
|
|
// String conversions.
|
|
// Returns 1 if the input string is an integer of the desired radix.
|
|
uint32_t str8_is_integer(String8 str, uint32_t radix) {
|
|
return str8_is_integer_internal(&str, radix);
|
|
}
|
|
|
|
uint64_t u64_from_str8(String8 str, uint32_t radix) {
|
|
return u64_from_str8_internal(&str, radix);
|
|
}
|
|
|
|
// Returns 0 if the conversion failed.
|
|
uint32_t try_f64_from_str8(String8 str, double* result) {
|
|
return try_f64_from_str8_internal(&str, result);
|
|
}
|
|
|
|
// API definition hook.
|
|
typedef void(*HotkeyPluginEditorFn)(EditorCtx*);
|
|
|
|
typedef struct HotkeyPluginEditorHook {
|
|
const char* name;
|
|
const char* description;
|
|
HotkeyPluginEditorFn fn;
|
|
} HotkeyPluginEditorHook;
|
|
|
|
// This is the 'ED' group.
|
|
#define DEF_PLUGIN_EDITOR_HOOK(ui_name, desc, fn_name) \
|
|
void ED_ ## fn_name ## _impl_fn(EditorCtx*); \
|
|
HotkeyPluginEditorHook ED_ ## fn_name = { .name = ui_name, .description = desc, .fn = &ED_ ## fn_name ## _impl_fn }; \
|
|
void ED_ ## fn_name ## _impl_fn(EditorCtx* ctx)
|
|
|
|
// Example plugins.
|
|
DEF_PLUGIN_EDITOR_HOOK("Says hello", "Says hello in each feed.", say_hello) {
|
|
String8 str = str8_lit("Hello, world!");
|
|
feed_queue_warning(str);
|
|
feed_queue_error(str);
|
|
Temp scratch = scratch_begin(NULL);
|
|
String8 msg = str8_fmt(scratch.arena, "Hello! This is an int '%d'. This is a char '%c'. This is a float '%f'. This is a string '%S'", 42, 'C', 3.1415, str);
|
|
feed_queue_info(msg);
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
DEF_PLUGIN_EDITOR_HOOK("Message demo", "Demos a formatted message to each feed.", message_demo) {
|
|
Temp scratch = scratch_begin(NULL);
|
|
String8 str = str8_lit("String");
|
|
String8 msg = str8_fmt(scratch.arena, "Hello! This is an int '%d'. This is a char '%c'. This is a float '%f'. This is a string '%S'", 42, 'C', 3.1415, str);
|
|
feed_queue_info(msg);
|
|
feed_queue_warning(msg);
|
|
feed_queue_error(msg);
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
DEF_PLUGIN_EDITOR_HOOK("Replace word with FRED", "Replaces the next word with 'FRED'", replace_w_fred) {
|
|
EditorCmd cmd = {0};
|
|
cmd.cmd = ED_NavWordRight;
|
|
cmd.flags = ED_FLG_UpdateSelection;
|
|
ed_push_command(ctx, &cmd);
|
|
cmd.cmd = ED_InsInsert;
|
|
cmd.flags = 0;
|
|
cmd.buf = str8_lit("FRED");
|
|
ed_push_command(ctx, &cmd);
|
|
}
|
|
|
|
DEF_PLUGIN_EDITOR_HOOK("Show selections", "Emits a message for each cursor selection", show_selections) {
|
|
Temp scratch = scratch_begin(NULL);
|
|
String8Array strings;
|
|
ed_cursor_selections(scratch.arena, ctx, &strings);
|
|
String8 msg = {0};
|
|
for EachIndex(i, strings.size) {
|
|
String8 str = strings.strs[i];
|
|
if (str.size != 0) {
|
|
msg = str8_fmt(scratch.arena, "[%d] = '%S'", i, str);
|
|
}
|
|
else {
|
|
msg = str8_fmt(scratch.arena, "[%d] = empty", i);
|
|
}
|
|
feed_queue_info(msg);
|
|
}
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
DEF_PLUGIN_EDITOR_HOOK("Change selection to snake case", "For each selection if it is PascalCase or camelCase, change it to snake_case", snake_case) {
|
|
Temp scratch = scratch_begin(NULL);
|
|
EditorCmd cmd = {0};
|
|
String8Array strings;
|
|
ed_cursor_selections(scratch.arena, ctx, &strings);
|
|
// First, let's allocate our result array which will be inserted as a command. The strings here will have lengths +1 for each capital letter.
|
|
String8Array ins_buf = {0};
|
|
ins_buf.size = strings.size;
|
|
ins_buf.strs = push_array(scratch.arena, String8, strings.size);
|
|
for EachIndex(i, strings.size) {
|
|
String8 str = strings.strs[i];
|
|
for EachIndex(j, str.size) {
|
|
// Skip the first index because we only care about internal characters.
|
|
if (j != 0
|
|
&& str.str[j] >= 'A'
|
|
&& str.str[j] <= 'Z') {
|
|
ins_buf.strs[i].size += 2;
|
|
}
|
|
else {
|
|
ins_buf.strs[i].size += 1;
|
|
}
|
|
}
|
|
}
|
|
// Now we can compute the result.
|
|
const uint32_t delta = 'a' - 'A';
|
|
char ins_char = 0;
|
|
for EachIndex(i, strings.size) {
|
|
String8 str = strings.strs[i];
|
|
ins_buf.strs[i].str = push_array_no_zero(scratch.arena, char, ins_buf.strs[i].size);
|
|
char* r_buf = ins_buf.strs[i].str;
|
|
for EachIndex(j, str.size) {
|
|
ins_char = str.str[j];
|
|
if (j != 0
|
|
&& str.str[j] >= 'A'
|
|
&& str.str[j] <= 'Z') {
|
|
*r_buf++ = '_';
|
|
ins_char = str.str[j] + delta;
|
|
}
|
|
else {
|
|
if (j == 0
|
|
&& str.str[j] >= 'A'
|
|
&& str.str[j] <= 'Z') {
|
|
ins_char = str.str[j] + delta;
|
|
}
|
|
else {
|
|
ins_char = str.str[j];
|
|
}
|
|
}
|
|
*r_buf++ = ins_char;
|
|
}
|
|
}
|
|
// Now we can compose the command.
|
|
cmd.cmd = ED_MCInsertGroup;
|
|
cmd.buffers = ins_buf;
|
|
ed_push_command(ctx, &cmd);
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
|
|
|
|
DEF_PLUGIN_EDITOR_HOOK("Page up and center", "Moves the screen a page up and centers the screen", page_up_center) {
|
|
EditorCmd cmd = {0};
|
|
cmd.cmd = ED_NavPageUp;
|
|
ed_push_command(ctx, &cmd);
|
|
cmd.cmd = ED_NavCenterCameraCursor;
|
|
ed_push_command(ctx, &cmd);
|
|
}
|
|
|
|
DEF_PLUGIN_EDITOR_HOOK("Page down and center", "Moves the screen a page down and centers the screen", page_down_center) {
|
|
EditorCmd cmd = {0};
|
|
cmd.cmd = ED_NavPageDown;
|
|
ed_push_command(ctx, &cmd);
|
|
cmd.cmd = ED_NavCenterCameraCursor;
|
|
ed_push_command(ctx, &cmd);
|
|
}
|
|
|
|
DEF_PLUGIN_EDITOR_HOOK("Trim file and save", "", trim_and_save) {
|
|
EditorCmd cmd = {0};
|
|
cmd.cmd = ED_SpecTrimLineEndings;
|
|
ed_push_command(ctx, &cmd);
|
|
cmd.cmd = ED_SaveRequestSave;
|
|
ed_push_command(ctx, &cmd);
|
|
}
|
|
|
|
DEF_PLUGIN_EDITOR_HOOK("Report line info", "Displays info about the current line.", line_info) {
|
|
Temp scratch = scratch_begin(NULL);
|
|
// Get the cursors first.
|
|
EditorCursorArray cursors = {0};
|
|
EditorCursorRange cursor_rng;
|
|
uint64_t line;
|
|
EditorOffsetRange line_rng;
|
|
String8 msg;
|
|
ed_cursor_ranges(scratch.arena, ctx, &cursors);
|
|
for EachIndex(i, cursors.size) {
|
|
cursor_rng = cursors.array[i];
|
|
line = ed_line_at_offset(ctx, cursor_rng.cursor_off);
|
|
ed_byte_range_at_line(ctx, line, &line_rng);
|
|
msg = str8_fmt(scratch.arena, "Cursor{%I64d} | Line{%I64d} | LineRng{%I64d, %I64d}",
|
|
cursor_rng.cursor_off, line, line_rng.first_off, line_rng.last_off);
|
|
feed_queue_info(msg);
|
|
}
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
DEF_PLUGIN_EDITOR_HOOK("Replace selection with numbers", "Replaces the current selection with ascending numbers for each subsequent occurrence.", repl_all_nums) {
|
|
Temp scratch = scratch_begin(NULL);
|
|
// Get the cursors first.
|
|
EditorCursorArray cursors = {0};
|
|
ed_cursor_ranges(scratch.arena, ctx, &cursors);
|
|
String8 repl_str = {0};
|
|
int good = 1;
|
|
if (cursors.size != 1) {
|
|
String8 msg = str8_lit("Operation only supported with a single cursor");
|
|
feed_queue_warning(msg);
|
|
good = 0;
|
|
}
|
|
|
|
// The range containing the original selection.
|
|
EditorOffsetRange off_rng;
|
|
if (good) {
|
|
EditorCursorRange rng = cursors.array[0];
|
|
off_rng.first_off = rng.sel.first_off;
|
|
off_rng.last_off = rng.sel.last_off;
|
|
ed_string_at_range(scratch.arena, ctx, &off_rng, &repl_str);
|
|
good = repl_str.size != 0;
|
|
}
|
|
|
|
// Now we're going to try and extract a 'starting' point for the numbering based on the selection.
|
|
// We do this by the following format:
|
|
// SELECTION_STARTNUM
|
|
// e.g.: 'FOOBAR_10'
|
|
// This would replace every instance of 'FOOBAR' with an ascending number starting at 10.
|
|
// Since the first instance will have extra characters, we save the first replacement string with
|
|
// 'initial_repl_str'.
|
|
String8 initial_repl_str = repl_str;
|
|
String8 sliced;
|
|
uint64_t count_start = 0;
|
|
if (good) {
|
|
// First, let's see if there's an '_'.
|
|
uint32_t found_int = 0;
|
|
for EachIndex(i, repl_str.size) {
|
|
if (repl_str.str[i] == '_') {
|
|
sliced = str8_substr(repl_str, .off = i + 1);
|
|
// Only support base-10 for now.
|
|
if (sliced.size != 0 && str8_is_integer(sliced, 10)) {
|
|
found_int = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (found_int) {
|
|
// Also remove '1' for the '_' character.
|
|
repl_str = str8_substr(repl_str, .len = repl_str.size - sliced.size - 1);
|
|
count_start = u64_from_str8(sliced, 10);
|
|
}
|
|
}
|
|
|
|
if (good) {
|
|
EditorBatchEdit batch;
|
|
// First, find every instance of the string above.
|
|
EditorFindResults find_results = {0};
|
|
ed_find_all(scratch.arena, ctx, &repl_str, 0, &find_results);
|
|
// Allocate enough space for the batch.
|
|
EditorBatchReplace repl_op = {0};
|
|
repl_op.size = find_results.size;
|
|
repl_op.array = push_array(scratch.arena, EditorReplaceData, repl_op.size);
|
|
// Fill.
|
|
for EachIndex(i, repl_op.size) {
|
|
// Since the first selection can be different, we will special case it.
|
|
if (find_results.array[i].first_off == off_rng.first_off) {
|
|
repl_op.array[i].range = off_rng;
|
|
}
|
|
else {
|
|
repl_op.array[i].range = find_results.array[i];
|
|
}
|
|
repl_op.array[i].buf = str8_fmt(scratch.arena, "%I64d", count_start);
|
|
++count_start;
|
|
}
|
|
// Iterate the lines and perform the batch replacement.
|
|
ed_edit_batch_begin(ctx, &batch);
|
|
ed_edit_batch_replace(&batch, &repl_op);
|
|
ed_edit_batch_end(ctx, &batch);
|
|
}
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
DEF_PLUGIN_EDITOR_HOOK("Toggle C-style comment line(s)", "Adds a C-style comment to selected lines or current line.", toggle_comment) {
|
|
Temp scratch = scratch_begin(NULL);
|
|
// Get the cursors first.
|
|
EditorCursorArray cursors = {0};
|
|
ed_cursor_ranges(scratch.arena, ctx, &cursors);
|
|
String8 repl_str = {0};
|
|
int good = 1;
|
|
if (cursors.size != 1) {
|
|
String8 msg = str8_lit("Operation only supported with a single cursor");
|
|
feed_queue_warning(msg);
|
|
good = 0;
|
|
}
|
|
|
|
uint64_t start_line;
|
|
uint64_t end_line;
|
|
uint32_t add_comment = 0;
|
|
if (good) {
|
|
// Find the line at the cursor selection. Note: The selection might also be empty, but that's OK.
|
|
start_line = ed_line_at_offset(ctx, cursors.array[0].sel.first_off);
|
|
end_line = ed_line_at_offset(ctx, cursors.array[0].sel.last_off);
|
|
// Pull the first line range and decide if we need to add a comment or remove one.
|
|
EditorOffsetRange line_rng;
|
|
ed_byte_range_at_line(ctx, start_line, &line_rng);
|
|
String8 line_txt;
|
|
ed_string_at_range(scratch.arena, ctx, &line_rng, &line_txt);
|
|
uint32_t slash_count = 0;
|
|
for (uint64_t i = 0; i < line_txt.size && i < 2; ++i) {
|
|
slash_count += line_txt.str[i] == '/';
|
|
}
|
|
add_comment = slash_count != 2;
|
|
|
|
// Create batch to iterate lines which either removes the comment or adds one.
|
|
// Note: For selected lines which do not already have a comment, we will leave
|
|
// them alone.
|
|
EditorBatchEdit batch;
|
|
ed_edit_batch_begin(ctx, &batch);
|
|
if (add_comment) {
|
|
// Note: Lines are inclusive ranges.
|
|
EditorBatchInsert ins = {0};
|
|
ins.size = (end_line - start_line) + 1;
|
|
ins.array = push_array(scratch.arena, EditorInsertData, ins.size);
|
|
uint64_t idx = 0;
|
|
for (uint64_t line = start_line; line <= end_line; ++line) {
|
|
ed_byte_range_at_line(ctx, line, &line_rng);
|
|
ins.array[idx].off = line_rng.first_off;
|
|
ins.array[idx].buf = str8_lit("//");
|
|
++idx;
|
|
}
|
|
ed_edit_batch_insert(&batch, &ins);
|
|
}
|
|
else {
|
|
// Note: Lines are inclusive ranges.
|
|
EditorBatchRemove rm = {0};
|
|
rm.size = (end_line - start_line) + 1;
|
|
rm.array = push_array(scratch.arena, EditorOffsetRange, rm.size);
|
|
uint64_t idx = 0;
|
|
for (uint64_t line = start_line; line <= end_line; ++line) {
|
|
ed_byte_range_at_line(ctx, line, &line_rng);
|
|
ed_string_at_range(scratch.arena, ctx, &line_rng, &line_txt);
|
|
if (line_txt.size > 1 && line_txt.str[0] == '/' && line_txt.str[1] == '/') {
|
|
rm.array[idx].first_off = line_rng.first_off;
|
|
rm.array[idx].last_off = line_rng.first_off + 2;
|
|
}
|
|
++idx;
|
|
}
|
|
ed_edit_batch_remove(&batch, &rm);
|
|
}
|
|
ed_edit_batch_end(ctx, &batch);
|
|
}
|
|
scratch_end(scratch);
|
|
} |