diff --git a/configs/fred/plugins.c b/configs/fred/plugins.c new file mode 100644 index 0000000..2f8e221 --- /dev/null +++ b/configs/fred/plugins.c @@ -0,0 +1,751 @@ +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); +} \ No newline at end of file