diff --git a/uppsrc/CodeEditor/CodeEditor.cpp b/uppsrc/CodeEditor/CodeEditor.cpp index e6a2e4a79..c50ea0087 100644 --- a/uppsrc/CodeEditor/CodeEditor.cpp +++ b/uppsrc/CodeEditor/CodeEditor.cpp @@ -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; diff --git a/uppsrc/CtrlLib/LineEdit.cpp b/uppsrc/CtrlLib/LineEdit.cpp index dac7000d7..383fafd01 100644 --- a/uppsrc/CtrlLib/LineEdit.cpp +++ b/uppsrc/CtrlLib/LineEdit.cpp @@ -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 op, Event 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 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; diff --git a/uppsrc/CtrlLib/Text.cpp b/uppsrc/CtrlLib/Text.cpp index 9fcce56ba..d43111441 100644 --- a/uppsrc/CtrlLib/Text.cpp +++ b/uppsrc/CtrlLib/Text.cpp @@ -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; } diff --git a/uppsrc/CtrlLib/TextEdit.h b/uppsrc/CtrlLib/TextEdit.h index ff4df5dcd..fb27e18f1 100644 --- a/uppsrc/CtrlLib/TextEdit.h +++ b/uppsrc/CtrlLib/TextEdit.h @@ -84,6 +84,7 @@ protected: int undoserial; bool rectsel; bool incundoserial; + bool column_typing = false; // group undos for column typing int undosteps; BiArray undo; BiArray 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 op, Event changesel = Null); struct RefreshDraw; friend class TextCtrl; diff --git a/uppsrc/CtrlLib/src.tpp/LineEdit_en-us.tpp b/uppsrc/CtrlLib/src.tpp/LineEdit_en-us.tpp index 2a8cf702e..1dda6fa28 100644 --- a/uppsrc/CtrlLib/src.tpp/LineEdit_en-us.tpp +++ b/uppsrc/CtrlLib/src.tpp/LineEdit_en-us.tpp @@ -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]()&] diff --git a/uppsrc/ide/Assist.cpp b/uppsrc/ide/Assist.cpp index f12b9a621..c6445ee53 100644 --- a/uppsrc/ide/Assist.cpp +++ b/uppsrc/ide/Assist.cpp @@ -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;