ide: Multiline cursor

This commit is contained in:
Mirek Fidler 2024-12-14 14:30:42 +01:00
parent 495535e10c
commit c0935e3ab9
6 changed files with 216 additions and 45 deletions

View file

@ -1093,6 +1093,8 @@ bool CodeEditor::Key(dword code, int count) {
Replace();
return true;
}
if(IsRectSelection())
return LineEdit::Key(code, count);
switch(code) {
case K_SHIFT_CTRL_UP:
SwapUpDown(true);
@ -1137,7 +1139,8 @@ bool CodeEditor::Key(dword code, int count) {
case K_SHIFT_CTRL_TAB:
return LineEdit::Key(K_TAB, count);
case K_ENTER:
IndentInsert('\n', count);
if(!IsRectSelection())
IndentInsert('\n', count);
return true;
}
bool sel = code & K_SHIFT;

View file

@ -61,7 +61,7 @@ void LineEdit::Clear() {
LineEdit& LineEdit::TabSize(int n) {
tabsize = n;
PlaceCaret0(GetColumnLine(cursor));
PlaceCaret0();
Refresh();
return *this;
}
@ -132,7 +132,8 @@ bool LineEdit::GetRectSelection(const Rect& rect, int line, int64& l, int64& h)
return false;
}
int LineEdit::RemoveRectSelection()
int LineEdit::RectSelectionOp(Event<int, Rect, int64, int64, WString&> op, Event<Rect&> changesel)
{
Rect rect = GetRectSelection();
WString txt;
@ -141,7 +142,7 @@ int LineEdit::RemoveRectSelection()
CacheLinePos(i);
GetRectSelection(rect, i, l, h);
WString s = GetWLine(i);
s.Remove(int(l - GetPos64(i)), int(h - l));
op(i, rect, l, h, s);
txt.Cat(s);
txt.Cat('\n');
}
@ -151,7 +152,115 @@ int LineEdit::RemoveRectSelection()
h++;
Remove((int)l, int(h - l));
Insert((int)l, txt);
return (int)GetGPos(rect.bottom, rect.left);
changesel(rect);
anchor = (int)GetGPos(rect.top, rect.left);
cursor = (int)GetGPos(rect.bottom, rect.left);
PlaceCaret0();
return cursor;
}
void LineEdit::RectSelectionChar(int c)
{
RectSelectionText(WString(c, 1));
}
void LineEdit::RectSelectionText(const WString& text)
{
if(GetRectSelection().GetWidth())
RemoveRectSelection();
int p = -1; // position after insertion, because of '\t' and double size chars cannot do just left+text.GetCount()
RectSelectionOp(
[&](int i, Rect rect, int64 l, int64 h, WString& s) {
int x = GetColumnLine(l).x;
int cursor;
if(x < rect.left) {
s.Cat(' ', rect.left - x);
s.Cat(text);
if(p < 0)
p = GetPos(i) + s.GetCount();
}
else {
s.Insert(int(l - GetPos64(i)), text);
if(p < 0) // first time the position is unchanged after whole text is replaced
p = l + text.GetCount();
}
},
[&](Rect& r) { r.left = GetColumnLine(p).x; }
);
column_typing = true;
}
void LineEdit::RectSelectionBackspace()
{
if(GetRectSelection().GetWidth())
RectSelectionDelete();
else {
int a = anchor;
RectSelectionLeftRight(-1, false);
if(a != anchor)
RectSelectionDelete();
}
}
void LineEdit::RectSelectionDelete()
{
Rect r = GetRectSelection();
if(r.GetWidth())
RemoveRectSelection();
else
RectSelectionOp(
[&](int i, Rect rect, int64 l, int64 h, WString& s) {
int p = int(l - GetPos64(i));
if(GetColumnLine(l).x == rect.left && p < s.GetCount())
s.Remove(p, 1);
},
[&](Rect& r) {}
);
}
void LineEdit::RectSelectionLeftRight(int dir, bool homeend)
{
Rect rect = GetRectSelection();
if(rect.left != rect.right) {
if(dir > 0)
rect.left = rect.right;
anchor = (int)GetGPos(rect.top, rect.left);
cursor = (int)GetGPos(rect.bottom, rect.left);
PlaceCaret0();
Refresh();
return;
}
auto Try = [&](int64& anchor, int64& cursor) {
Point a0 = GetColumnLine(anchor);
int64 a = clamp(anchor + dir, (int64)0, GetLength64());
Point a1 = GetColumnLine(a);
if(a1.y != a0.y)
return false;
int c = (int)GetGPos(GetColumnLine(cursor).y, a1.x);
if(GetColumnLine(c).x == a1.x) {
anchor = a;
cursor = c;
PlaceCaret0();
return true;
}
return false;
};
int n = homeend ? 1000 : 1;
while(n > 0 && (Try(anchor, cursor) || Try(cursor, anchor)))
n--;
}
int LineEdit::RemoveRectSelection()
{
return RectSelectionOp(
[&](int i, Rect, int64 l, int64 h, WString& s) { s.Remove(int(l - GetPos64(i)), int(h - l)); }
);
}
WString LineEdit::CopyRectSelection()
@ -175,20 +284,23 @@ WString LineEdit::CopyRectSelection()
int LineEdit::PasteRectSelection(const WString& s)
{
Vector<WString> cl = Split(s, '\n', false);
Rect rect = GetRectSelection();
int64 pos = cursor;
int n = 0;
for(int i = 0; i < cl.GetCount() && rect.top + i <= rect.bottom; i++) {
int64 l, h;
CacheLinePos(i);
GetRectSelection(rect, i + rect.top, l, h);
Remove((int)l, int(h - l));
int nn = Insert((int)l, cl[i]);
n += nn;
pos = l + nn;
int cursor0 = cursor;
if(cl.GetCount() == 1)
RectSelectionText(cl[0]);
else {
Rect rect = GetRectSelection();
int64 pos = cursor;
for(int i = 0; i < cl.GetCount() && rect.top + i <= rect.bottom; i++) {
int64 l, h;
CacheLinePos(i);
GetRectSelection(rect, i + rect.top, l, h);
Remove((int)l, int(h - l));
int nn = Insert((int)l, cl[i]);
pos = l + nn;
}
PlaceCaret(pos);
}
PlaceCaret(pos);
return n;
return cursor - cursor0;
}
void LineEdit::PasteColumn(const WString& text)
@ -219,10 +331,10 @@ void LineEdit::PasteColumn(const WString& text)
int li = p.y + i;
if(li < GetLineCount()) {
int l = (int)GetGPos(i + p.y, p.x);
pos = l + Insert(l, cl[i]);
pos = l + Insert(l, WString(' ', max(p.x - GetColumnLine(l).x, 0)) + cl[i]);
}
else {
Insert(GetLength32(), cl[i] + "\n");
Insert(GetLength32(), "\n" + WString(' ', p.x) + cl[i]);
pos = GetLength32();
}
}
@ -626,8 +738,8 @@ void LineEdit::Paint0(Draw& w)
if(!IsNull(vline))
rw.DrawRect(caretpos.x, y, 1, fsz.cy, vline);
}
if(rectsel && rect.left == rect.right && i >= rect.top && i <= rect.bottom)
rw.DrawRect(rect.left * fsz.cx - scx, y, 2, fsz.cy, Blend(color[PAPER_SELECTED], color[PAPER_NORMAL]));
// if(rectsel && rect.left == rect.right && i >= rect.top && i <= rect.bottom)
// rw.DrawRect(rect.left * fsz.cx - scx, y, 2, fsz.cy, Blend(color[PAPER_SELECTED], color[PAPER_NORMAL]));
}
}
}
@ -814,15 +926,24 @@ void LineEdit::AlignChar() {
Rect LineEdit::GetCaret() const
{
if(overwrite)
if(overwrite && !IsRectSelection())
return RectC(caretpos.x, caretpos.y + fsz.cy - 2, fsz.cx, 2);
else
return RectC(caretpos.x, caretpos.y, block_caret? fsz.cx : 2, fsz.cy);
return RectC(caretpos.x, caretpos.y, block_caret? fsz.cx : 2, fsz.cy * caretlines);
}
void LineEdit::PlaceCaret0(Point p) {
void LineEdit::PlaceCaret0()
{
Point p = GetColumnLine(cursor);
Size fsz = GetFontSize();
p -= sb;
caretlines = 1;
if(IsRectSelection()) {
Point ap = GetColumnLine(anchor) - sb;
if(ap.y < p.y)
Swap(ap.y, p.y);
caretlines = ap.y - p.y + 1;
}
caretpos = Point(p.x * fsz.cx, p.y * fsz.cy);
}
@ -853,7 +974,7 @@ int LineEdit::PlaceCaretNoG(int64 newcursor, bool sel) {
RefreshLine(p.y);
cursor = newcursor;
ScrollIntoCursor();
PlaceCaret0(p);
PlaceCaret0();
SelectionChanged();
WhenSel();
if(IsAnySelection())
@ -882,7 +1003,7 @@ void LineEdit::CenterCursor() {
}
void LineEdit::Scroll() {
PlaceCaret0(GetColumnLine(cursor));
PlaceCaret0();
scroller.Scroll(*this, GetSize(), sb.Get(), GetFontSize());
SetHBar();
NewScrollPos();
@ -1040,6 +1161,8 @@ void LineEdit::MoveTextEnd(bool sel) {
bool LineEdit::InsertChar(dword key, int count, bool canow) {
if(key == K_TAB && !processtab)
return false;
if(findarg(key, '\n', K_ENTER) >= 0 && IsRectSelection())
return true;
if(filter && key >= 32 && key < K_CHAR_LIM)
key = (*filter)(key);
if(!IsReadOnly() && (key >= 32 && key < K_CHAR_LIM || key == '\t' || key == '\n' ||
@ -1047,6 +1170,10 @@ bool LineEdit::InsertChar(dword key, int count, bool canow) {
if(key >= 128 && key < K_CHAR_LIM && (charset != CHARSET_UTF8 && charset != CHARSET_UTF8_BOM)
&& FromUnicode((wchar)key, charset) == DEFAULTCHAR)
return true;
if(IsRectSelection()) {
RectSelectionChar(key);
return true;
}
if(!RemoveSelection() && overwrite && key != '\n' && key != K_ENTER && canow) {
int64 q = cursor;
int i = GetLinePos64(q);
@ -1178,6 +1305,8 @@ bool LineEdit::Key(dword key, int count) {
}
bool sel = key & K_SHIFT;
dorectsel = key & K_ALT;
if(IsRectSelection() && sel)
dorectsel = true;
dword k = key & ~K_SHIFT;
if((key & (K_SHIFT|K_ALT)) == (K_SHIFT|K_ALT))
k &= ~K_ALT;
@ -1193,15 +1322,31 @@ bool LineEdit::Key(dword key, int count) {
break;
}
case K_LEFT:
if(IsRectSelection() && !dorectsel) {
RectSelectionLeftRight(-1, false);
break;
}
MoveLeft(sel);
break;
case K_RIGHT:
if(IsRectSelection() && !dorectsel) {
RectSelectionLeftRight(1, false);
break;
}
MoveRight(sel);
break;
case K_HOME:
if(IsRectSelection() && !dorectsel) {
RectSelectionLeftRight(-1, true);
break;
}
MoveHome(sel);
break;
case K_END:
if(IsRectSelection() && !dorectsel) {
RectSelectionLeftRight(1, true);
break;
}
MoveEnd(sel);
break;
case K_UP:
@ -1231,15 +1376,25 @@ bool LineEdit::Key(dword key, int count) {
case K_CTRL_A:
SelectAll();
break;
case K_ALT_KEY|K_KEYUP:
return IsRectSelection(); // prevent opening menu on Alt+Click
default:
dorectsel = false;
if(IsReadOnly())
return MenuBar::Scan(WhenBar, key);
switch(key) {
case K_DELETE:
if(IsRectSelection() && !dorectsel) {
RectSelectionDelete();
break;
}
DeleteChar();
break;
case K_BACKSPACE:
if(IsRectSelection() && !dorectsel) {
RectSelectionBackspace();
break;
}
case K_SHIFT|K_BACKSPACE:
Backspace();
break;

View file

@ -58,6 +58,7 @@ void TextCtrl::MiddleDown(Point p, dword flags)
void TextCtrl::CancelMode()
{
selclick = false;
column_typing = false;
dropcaret = Null;
isdrag = false;
}
@ -903,7 +904,10 @@ void TextCtrl::Undodo()
void TextCtrl::NextUndo()
{
undoserial += incundoserial;
if(column_typing)
column_typing = false;
else
undoserial += incundoserial;
incundoserial = false;
}
@ -934,7 +938,7 @@ void TextCtrl::DecDirty() {
int TextCtrl::InsertU(int pos, const WString& txt, bool typing) {
int sz = Insert0(pos, txt);
if(undosteps) {
if(undo.GetCount() > 1 && typing && *txt != '\n' && IsDirty()) {
if(undo.GetCount() && typing && *txt != '\n' && IsDirty()) {
UndoRec& u = undo.Tail();
if(u.typing && u.pos + u.size == pos) {
u.size += txt.GetLength();
@ -1096,15 +1100,15 @@ bool TextCtrl::RemoveSelection() {
int64 l, h;
if(anchor < 0) return false;
if(IsRectSelection())
l = RemoveRectSelection();
RemoveRectSelection();
else {
if(!GetSelection(l, h))
return false;
Remove((int)l, int(h - l));
anchor = -1;
Refresh();
PlaceCaret(l);
}
anchor = -1;
Refresh();
PlaceCaret(l);
Action();
return true;
}

View file

@ -84,6 +84,7 @@ protected:
int undoserial;
bool rectsel;
bool incundoserial;
bool column_typing = false; // group undos for column typing
int undosteps;
BiArray<UndoRec> undo;
BiArray<UndoRec> redo;
@ -349,6 +350,7 @@ protected:
int vlinex;
Scroller scroller;
Point caretpos;
int caretlines = 1;
bool nohbar;
bool showtabs;
bool cutline;
@ -366,7 +368,7 @@ protected:
void MovePage(int dir, bool sel);
void PlaceCaret0(Point p);
void PlaceCaret0();
int PlaceCaretNoG(int64 newcursor, bool sel = false);
void Scroll();
@ -376,6 +378,12 @@ protected:
void DoPasteColumn() { PasteColumn(); }
void SyncFont();
bool IsDoubleChar(int ch) const;
void RectSelectionChar(int c);
void RectSelectionText(const WString& text);
void RectSelectionBackspace();
void RectSelectionDelete();
void RectSelectionLeftRight(int dir, bool homeend);
int RectSelectionOp(Event<int, Rect, int64, int64, WString&> op, Event<Rect&> changesel = Null);
struct RefreshDraw;
friend class TextCtrl;

View file

@ -64,8 +64,8 @@ t]_[*@3 p])_[@(0.0.255) const]&]
[s2;%% Get the the offset of character placed at [%-*@3 p].&]
[s3; &]
[s4; &]
[s5;:LineEdit`:`:GetColumnLine`(int`)const: [_^Point^ Point]_[* GetColumnLine]([@(0.0.255) i
nt]_[*@3 pos])_[@(0.0.255) const]&]
[s5;:Upp`:`:LineEdit`:`:GetColumnLine`(int64`)const: Point [* GetColumnLine](int64
[*@3 pos]) [@(0.0.255) const]&]
[s2;%% Returns the line and column for the character at [%-*@3 pos]
accounting for any tabulators. Column is x member of resulting
Point, line is y.&]
@ -77,8 +77,8 @@ oint]_[*@3 pos])_[@(0.0.255) const]&]
Does account for tabulators.&]
[s3; &]
[s4; &]
[s5;:LineEdit`:`:GetIndexLine`(int`)const: [_^Point^ Point]_[* GetIndexLine]([@(0.0.255) in
t]_[*@3 pos])_[@(0.0.255) const]&]
[s5;:Upp`:`:LineEdit`:`:GetIndexLine`(int64`)const: Point [* GetIndexLine](int64
[*@3 pos]) [@(0.0.255) const]&]
[s2;%% Returns the line and index of character in the line for the
given [%-*@3 pos]. Does not account for tabulators.&]
[s3; &]
@ -89,8 +89,8 @@ oint]_[*@3 pos])_[@(0.0.255) const]&]
Does not account for tabulators.&]
[s3; &]
[s4; &]
[s5;:LineEdit`:`:SetRectSelection`(int`,int`): [@(0.0.255) void]_[* SetRectSelection]([@(0.0.255) i
nt]_[*@3 l], [@(0.0.255) int]_[*@3 h])&]
[s5;:Upp`:`:LineEdit`:`:SetRectSelection`(int64`,int64`): [@(0.0.255) void]
[* SetRectSelection](int64 [*@3 l], int64 [*@3 h])&]
[s2;%% Sets rectangular selection.&]
[s3;%% &]
[s4; &]
@ -105,9 +105,10 @@ onst]&]
[s2;%% Returns rectangular selection (as `"graphical`").&]
[s3; &]
[s4; &]
[s5;:LineEdit`:`:GetRectSelection`(const Rect`&`,int`,int`&`,int`&`): [@(0.0.255) bool]_
[* GetRectSelection]([@(0.0.255) const]_[_^Rect^ Rect][@(0.0.255) `&]_[*@3 rect],
[@(0.0.255) int]_[*@3 line], [@(0.0.255) int`&]_[*@3 l], [@(0.0.255) int]_`&[*@3 h])&]
[s5;:Upp`:`:LineEdit`:`:GetRectSelection`(const Rect`&`,int`,int64`&`,int64`&`): [@(0.0.255) b
ool] [* GetRectSelection]([@(0.0.255) const] Rect[@(0.0.255) `&] [*@3 rect],
[@(0.0.255) int] [*@3 line], int64[@(0.0.255) `&] [*@3 l], int64[@(0.0.255) `&]
[*@3 h])&]
[s2;%% Returns lower and upper limits [%-*@3 l] [%-*@3 h] of characters
of [%-*@3 line] that are in rectangular selection [%-*@3 rect]. Returns
false when line is not in selection.&]
@ -217,7 +218,7 @@ the text).&]
[s5;:LineEdit`:`:PasteColumn`(const WString`&`): [@(0.0.255) void]_[* PasteColumn]([@(0.0.255) c
onst]_[_^WString^ WString][@(0.0.255) `&]_[*@3 text])&]
[s2;%% Pastes lines of [%-*@3 text] into actual graphical column of
text.&]
text. Returns final cursor position.&]
[s3;%% &]
[s4; &]
[s5;:LineEdit`:`:PasteColumn`(`): [@(0.0.255) void]_[* PasteColumn]()&]

View file

@ -1074,7 +1074,7 @@ bool AssistEditor::Key(dword key, int count)
bool b = CodeEditor::Key(key, count);
if(b && search.HasFocus())
SetFocus();
if(IsReadOnly())
if(IsReadOnly() || IsRectSelection())
return b;
if(assist.IsOpen()) {
bool (*test)(int c) = include_assist ? isincludefnchar : isaid;