Fix the line moving, really hard code ...

This commit is contained in:
Krzosa Karol
2026-01-24 13:48:08 +01:00
parent ba6de21396
commit dc839cb3e0
7 changed files with 183 additions and 118 deletions

View File

@@ -1,4 +1,4 @@
#define BUFFER_DEBUG 0
#define BUFFER_DEBUG DEBUG_BUILD
API Range MakeRange(Int a, Int b) {
Range result = {Min(a, b), Max(a, b)};
@@ -669,6 +669,9 @@ void UpdateLines(Buffer *buffer, Range range, String16 string) {
}
void RawValidateLineStarts(Buffer *buffer) {
if (buffer->no_line_starts) {
return;
}
Int line = 0;
for (Int i = 0; i < buffer->len; i += 1) {
Int l = PosToLine(buffer, i);
@@ -795,7 +798,9 @@ void MergeSort(int64_t Count, T *First, T *Temp) {
API void ApplyEditsMultiCursor(Buffer *buffer, Array<Edit> edits) {
ProfileFunction();
#if BUFFER_DEBUG
if (buffer->no_line_starts == false) {
Assert(buffer->line_starts.len);
}
Assert(edits.len);
For(edits) {
Assert(it.range.min >= 0);
@@ -1038,7 +1043,9 @@ API void AdjustCarets(Array<Edit> edits, Array<Caret> *carets) {
for (Int i = 0; i < carets->len; i += 1) carets->data[i] = new_carets[i];
}
API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool kill_selection) {
constexpr bool EndEdit_KillSelection = true;
constexpr bool EndEdit_SkipFixingCaretsIWantToDoThatMyself = true;
API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool kill_selection, bool skip_fixing_carets_user_will_do_that_himself = false) {
ProfileFunction();
{
@@ -1068,6 +1075,7 @@ API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool
}
#endif
if (skip_fixing_carets_user_will_do_that_himself == false) {
// Adjust carets
// this one also moves the carets forward if they are aligned with edit
//
@@ -1105,6 +1113,7 @@ API void EndEdit(Buffer *buffer, Array<Edit> *edits, Array<Caret> *carets, bool
}
}
}
}
// Merge carets that overlap, this needs to be handled before any edits to
// make sure overlapping edits won't happen.
@@ -1322,7 +1331,7 @@ void RunBufferTest() {
AddEdit(&edits, {0, 7}, u"t");
AddEdit(&edits, {8, 9}, u"T");
AddEdit(&edits, GetBufferEndAsRange(&buffer), u"\nnewThing");
EndEdit(&buffer, &edits, &carets, KILL_SELECTION);
EndEdit(&buffer, &edits, &carets, EndEdit_KillSelection);
String16 s = GetString(&buffer);
Assert(s == u"t\nThings\nnewThing");
DeinitBuffer(&buffer);
@@ -1346,7 +1355,7 @@ void RunBufferTest() {
AddEdit(&edits, {0, 7}, u"t");
AddEdit(&edits, {8, 9}, u"T");
AddEdit(&edits, GetBufferEndAsRange(&buffer), u"\nnewThing");
EndEdit(&buffer, &edits, &carets, KILL_SELECTION);
EndEdit(&buffer, &edits, &carets, EndEdit_KillSelection);
String16 s = GetString(&buffer);
Assert(s == u"t\nThings\nnewThing");
Assert(buffer.line_starts.len == 0);

View File

@@ -141,7 +141,7 @@ void TrimWhitespace(Buffer *buffer, bool trim_lines_with_caret) {
Array<Range> lines_to_skip_triming = {};
if (!trim_lines_with_caret) {
lines_to_skip_triming = GetSelectedLinesSorted(scratch, view);
lines_to_skip_triming = GetSelectedLinesSortedExclusive(scratch, view);
}
for (Int i = 0; i < buffer->line_starts.len; i += 1) {
@@ -160,7 +160,7 @@ void TrimWhitespace(Buffer *buffer, bool trim_lines_with_caret) {
AddEdit(&edits, whitespace_range, u"");
}
EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION);
EndEdit(buffer, &edits, &view->carets, !EndEdit_KillSelection);
view->update_scroll = false;
}
@@ -207,7 +207,7 @@ void CMD_FormatSelection() {
String16 string16 = {exec_result.buffer->str, exec_result.buffer->len};
AddEdit(&edits, it.range, string16);
}
EndEdit(primary.buffer, &edits, &primary.view->carets, KILL_SELECTION);
EndEdit(primary.buffer, &edits, &primary.view->carets, EndEdit_KillSelection);
} RegisterCommand(CMD_FormatSelection, "", "");
void CMD_KillProcess() {
@@ -218,12 +218,14 @@ void CMD_KillProcess() {
void CMD_MakeFontLarger() {
FontSize += 1;
ReloadFont(PathToFont, (U32)FontSize);
CMD_CenterView();
} RegisterCommand(CMD_MakeFontLarger, "ctrl-equals", "Increase the font size");
void CMD_MakeFontSmaller() {
if (FontSize > 4) {
FontSize -= 1;
ReloadFont(PathToFont, (U32)FontSize);
CMD_CenterView();
}
} RegisterCommand(CMD_MakeFontSmaller, "ctrl-minus", "Decrease the font size");

View File

@@ -72,7 +72,7 @@ void ClipboardPaste(View *view) {
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
MergeCarets(buffer, &view->carets);
For(view->carets) AddEdit(&edits, it.range, string);
EndEdit(buffer, &edits, &view->carets, KILL_SELECTION);
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
return;
}
@@ -84,7 +84,7 @@ void ClipboardPaste(View *view) {
Caret &it = view->carets[i];
AddEdit(&edits, it.range, string);
}
EndEdit(buffer, &edits, &view->carets, KILL_SELECTION);
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
}
void CMD_Paste() {

View File

@@ -10,6 +10,14 @@ bool SearchCaseSensitive = false;
bool SearchWordBoundary = false;
bool BreakOnError = false;
Int ErrorCount;
// String16 InitialScratchContent;
String16 InitialScratchContent = uR"==(0
1
2
3
4
5
6)==";
Allocator SysAllocator = {SystemAllocatorProc};
String ConfigDir;
@@ -32,6 +40,7 @@ Array<Buffer *> Buffers;
View *LogView;
Buffer *LogBuffer;
// Replace with ref to null buffer?
BufferID NullBufferID;
ViewID NullViewID;
WindowID NullWindowID;

View File

@@ -963,6 +963,9 @@ int main(int argc, char **argv)
View *null_view = CreateView(null_buffer->id);
null_view->special = true;
Assert(null_buffer->id == NullBufferID && null_view->id == NullViewID);
if (InitialScratchContent.len) {
RawAppend(null_buffer, InitialScratchContent);
}
Buffer *logs_buffer = CreateBuffer(sys_allocator, GetUniqueBufferName(ProjectDirectory, "logs", ""));
logs_buffer->special = true;

View File

@@ -6,7 +6,10 @@ struct ViewID { Int id; View *o; };
struct WindowID { Int id; Window *o; };
union Range { struct { Int min; Int max; }; Int e[2]; };
struct Caret { union { Range range; Int pos[2]; }; Int ifront;};
struct XY { Int col; Int line; };
union XY {
struct {Int col; Int line;};
struct {Int x; Int y; };
};
typedef void Function();
struct FunctionData {
@@ -314,7 +317,6 @@ constexpr int DIR_UP = 3;
constexpr int DIR_COUNT = 4;
constexpr bool CTRL_PRESSED = true;
constexpr bool SHIFT_PRESS = true;
constexpr bool KILL_SELECTION = true;
constexpr Int LAST_LINE = INT64_MAX;
BSet GetBSet(struct Window *window);

View File

@@ -172,6 +172,34 @@ String GetIndentString8(Allocator allocator, Int indent_size) {
return res;
}
Array<Range> GetSelectedLinesSortedExclusive(Allocator allocator, View *view) {
Scratch scratch(allocator);
Buffer *buffer = GetBuffer(view->active_buffer);
Array<Caret> caret_copy = TightCopy(scratch, view->carets);
Array<Caret> temp = TightCopy(scratch, view->carets);
if (view->carets.len > 1) MergeSort(view->carets.len, caret_copy.data, temp.data);
Array<Range> result = {allocator};
For(caret_copy) {
Int min_line = PosToLine(buffer, it.range.min);
Int max_line = PosToLine(buffer, it.range.max);
Range line_range = {min_line, max_line + 1};
if (result.len == 0) {
Add(&result, line_range);
continue;
}
Range *last = GetLast(result);
if (AreOverlapping(*last, line_range)) {
last->max = Max(last->max, line_range.max);
} else {
Add(&result, line_range);
}
}
return result;
}
void IndentedNewLine(View *view) {
Buffer *buffer = GetBuffer(view->active_buffer);
Scratch scratch;
@@ -185,7 +213,7 @@ void IndentedNewLine(View *view) {
String16 string16 = ToString16(scratch, string);
AddEdit(&edits, it.range, string16);
}
EndEdit(buffer, &edits, &view->carets, KILL_SELECTION);
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
}
// WARNING: Don't use in user facing stuff
@@ -399,52 +427,90 @@ void MoveCarets(View *view, int direction, bool ctrl = false, bool shift = false
}
void MoveCaretsLine(View *view, int direction) {
// This is so hard...
// It's because of the corner cases in the implementation, would be nice to simplify this somehow
// but don't know how. The multiple carets constraints + not every line ends with new line make it into
// really tricky code.
Assert(direction == DIR_DOWN || direction == DIR_UP);
Scratch scratch;
// @todo: this doesn't work well at the end of buffer
struct XYPair {
XY front;
XY back;
};
Buffer *buffer = GetBuffer(view->active_buffer);
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
MergeCarets(buffer, &view->carets);
// Save caret positions to fix them at end to the expected incremented by one positions
struct XYPair { XY front; XY back; };
Array<XYPair> saved_xy = {scratch};
For (view->carets) {
Int eof_current = 0;
Range lines_to_move_range = {GetFullLineStart(buffer, it.range.min), GetFullLineEnd(buffer, it.range.max, &eof_current)};
if (lines_to_move_range.min == 0 && direction == DIR_UP) {
continue;
}
Int eof = 0;
Int next_line_start = lines_to_move_range.max;
Int next_line_end = GetFullLineEnd(buffer, next_line_start, &eof);
Int prev_line_end = lines_to_move_range.min - 1;
Int prev_line_start = GetFullLineStart(buffer, prev_line_end);
if (direction == DIR_DOWN && eof) {
continue;
} else if (direction == DIR_UP && eof_current) {
continue;
}
String16 string = Copy16(scratch, GetString(buffer, lines_to_move_range));
AddEdit(&edits, lines_to_move_range, {});
if (direction == DIR_DOWN) {
AddEdit(&edits, MakeRange(next_line_end), string);
} else {
AddEdit(&edits, MakeRange(prev_line_start), string);
}
Add(&saved_xy, {PosToXY(buffer, GetFront(it)), PosToXY(buffer, GetBack(it))});
}
EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION);
Int line_offset = direction == DIR_UP ? -1 : +1;
int side_idx = direction == DIR_UP ? 0 : 1;
Array<Range> line_ranges = GetSelectedLinesSortedExclusive(scratch, view); // This is <min_line, max_line> not positions
Array<Edit> edits = BeginEdit(scratch, buffer, view->carets);
MergeCarets(buffer, &view->carets);
ForItem (_lines, line_ranges) {
Range lines = {_lines.min, _lines.max - 1}; // inclusive
Int swap_line = lines.e[side_idx] + line_offset;
Range total_range = {};
{
Int total_min_line = 0;
Int total_max_line = 0;
if (direction == DIR_UP) {
total_min_line = swap_line;
total_max_line = lines.max;
if (total_min_line < 0) {
edits.len = 0;
saved_xy.len = 0;
break;
}
} else {
total_min_line = lines.min;
total_max_line = swap_line;
if (total_max_line >= buffer->line_starts.len) {
edits.len = 0;
saved_xy.len = 0;
break;
}
}
Range arange = GetLineRange(buffer, total_min_line);
Range brange = GetLineRange(buffer, total_max_line);
total_range = {arange.min, brange.max};
}
String16 replacement_string = {};
{
Range selected_range_min = GetLineRange(buffer, lines.min);
Range selected_range_max = GetLineRange(buffer, lines.max);
Range swap_line_range = GetLineRange(buffer, swap_line);
String16 selected_string = GetString(buffer, {selected_range_min.min, selected_range_max.max});
String16 swap_string = GetString(buffer, swap_line_range);
if (direction == DIR_UP) {
bool ends_with_new_line = selected_string.len == 0 || (selected_string.len && selected_string[selected_string.len - 1] == u'\n');
bool doesnt_end_with_new_line_special_case = !ends_with_new_line;
if (doesnt_end_with_new_line_special_case) {
if (swap_string.len) swap_string.len -= 1;
selected_string = Concat(scratch, selected_string, u"\n");
}
replacement_string = Concat(scratch, selected_string, swap_string);
} else {
bool ends_with_new_line = swap_string.len == 0 || (swap_string.len && swap_string[swap_string.len - 1] == u'\n');
bool doesnt_end_with_new_line_special_case = !ends_with_new_line;
if (doesnt_end_with_new_line_special_case) {
if (selected_string.len) selected_string.len -= 1;
swap_string = Concat(scratch, swap_string, u"\n");
}
replacement_string = Concat(scratch, swap_string, selected_string);
}
}
AddEdit(&edits, total_range, replacement_string);
}
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection, EndEdit_SkipFixingCaretsIWantToDoThatMyself);
for (Int i = 0; i < saved_xy.len; i += 1) {
Caret &caret = view->carets[i];
XYPair &xypair = saved_xy[i];
@@ -452,9 +518,10 @@ void MoveCaretsLine(View *view, int direction) {
xypair.back.line += line_offset;
Int front = XYToPos(buffer, xypair.front);
Int back = XYToPos(buffer, xypair.back);
caret = MakeCaret(front, back);
}
MergeCarets(buffer, &view->carets);
IF_DEBUG(AssertRanges(view->carets));
}
void CreateCursorVertical(View *view, int direction) {
@@ -514,7 +581,7 @@ void Delete(View *view, int direction, bool ctrl = false) {
MergeCarets(buffer, &view->carets);
For(view->carets) AddEdit(&edits, it.range, {});
EndEdit(buffer, &edits, &view->carets, true);
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
}
void EncloseSpace(View *view) {
@@ -525,34 +592,6 @@ void EncloseSpace(View *view) {
}
}
Array<Range> GetSelectedLinesSorted(Allocator allocator, View *view) {
Scratch scratch(allocator);
Buffer *buffer = GetBuffer(view->active_buffer);
Array<Caret> caret_copy = TightCopy(scratch, view->carets);
Array<Caret> temp = TightCopy(scratch, view->carets);
if (view->carets.len > 1) MergeSort(view->carets.len, caret_copy.data, temp.data);
Array<Range> result = {allocator};
For(caret_copy) {
Int min_line = PosToLine(buffer, it.range.min);
Int max_line = PosToLine(buffer, it.range.max);
Range line_range = {min_line, max_line + 1};
if (result.len == 0) {
Add(&result, line_range);
continue;
}
Range *last = GetLast(result);
if (AreOverlapping(*last, line_range)) {
last->max = Max(last->max, line_range.max);
} else {
Add(&result, line_range);
}
}
return result;
}
void IndentSelectedLines(View *view, bool shift = false) {
Scratch scratch;
Buffer *buffer = GetBuffer(view->active_buffer);
@@ -562,7 +601,7 @@ void IndentSelectedLines(View *view, bool shift = false) {
char16_t indent_char = GetIndentChar();
String16 indent_string = GetIndentString(scratch, IndentSize);
Array<Range> line_ranges_to_indent = GetSelectedLinesSorted(scratch, view);
Array<Range> line_ranges_to_indent = GetSelectedLinesSortedExclusive(scratch, view);
For(line_ranges_to_indent) {
for (Int i = it.min; i < it.max; i += 1) {
Range pos_range_of_line = GetLineRange(buffer, i);
@@ -580,7 +619,7 @@ void IndentSelectedLines(View *view, bool shift = false) {
}
}
}
EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION);
EndEdit(buffer, &edits, &view->carets, !EndEdit_KillSelection);
view->update_scroll = false;
}
@@ -617,7 +656,7 @@ Array<Edit> ReplaceEx(Allocator scratch, View *view, String16 string) {
For(view->carets) {
AddEdit(&edits, it.range, string);
}
EndEdit(buffer, &edits, &view->carets, KILL_SELECTION);
EndEdit(buffer, &edits, &view->carets, EndEdit_KillSelection);
return edits;
}
@@ -646,8 +685,9 @@ void DuplicateLine(View *view, int direction) {
Int pos = direction == DIR_UP ? range.min : range.max;
AddEdit(&edits, MakeRange(pos), string);
}
EndEdit(buffer, &edits, &view->carets, !KILL_SELECTION);
EndEdit(buffer, &edits, &view->carets, !EndEdit_KillSelection);
// Move carets now in the duplicate direction
Int coef = direction == DIR_UP ? -1 : 1;
for (Int i = 0; i < edits.len; i += 1) {
Caret *caret = view->carets.data + i;