fred plugins file

This commit is contained in:
2026-05-07 20:19:51 +01:00
parent 661b0e731f
commit e187ff8203
+751
View File
@@ -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);
}