1 namespace gui::controls;
4 selectionForeground = white;
5 disabled: defaultTextColor = Color { 85, 85, 85 };
14 public class SyntaxColorScheme
18 Color charLiteralColor;
19 Color stringLiteralColor;
20 Color preprocessorColor;
22 private Array<Color> keywordColors { };
24 public property Container<Color> keywordColors
26 set { keywordColors.Copy((void *)value); }
27 get { return keywordColors; }
35 #define XOFFSET (3 + (/*style.lineNumbers?6 * this.space.w:*/0))
37 #define YOFFSET (style.multiLine ? 1 : ((clientSize.h + 1 - space.h) / 2))
39 #define IS_ALUNDER(ch) (/*(ch) == '_' || */CharMatchCategories((ch), letters|numbers|marks|connector))
41 #define UTF8_IS_FIRST(x) (__extension__({ byte b = x; (!(b) || !((b) & 0x80) || ((b) & 0x40)); }))
42 #define UTF8_NUM_BYTES(x) (__extension__({ byte b = x; (b & 0x80 && b & 0x40) ? ((b & 0x20) ? ((b & 0x10) ? 4 : 3) : 2) : 1; }))
43 #define UTF8_GET_CHAR(string, numBytes) \
47 byte b = (string)[0]; \
50 numBytes = b ? 1 : 0; \
64 if(b & 0x08) { numBytes = 0; } \
73 for(i = 0; i<numBytes; i++) \
76 ch |= (b = (string)[i]) & mask; \
78 if(i > 1 && (!(b & 0x80) || (b & 0x40))) \
85 ch > 0x10FFFF || (ch >= 0xD800 && ch <= 0xDFFF) || \
86 (ch < 0x80 && numBytes > 1) || \
87 (ch < 0x800 && numBytes > 2) || \
88 (ch < 0x10000 && numBytes > 3))\
98 bool autoEmpty:1, readOnly:1, multiLine:1, stuckCaret:1, freeCaret:1, select:1, hScroll:1, vScroll:1, smartHome:1;
99 bool noCaret:1, noSelect:1, tabKey:1, useTab:1, tabSel:1, allCaps:1, syntax:1, wrap:1;
102 bool inMultiLineComment:1, inPrep:1, escaped:1, continuedSingleLineComment:1, wasInMultiLine:1, continuedString:1, continuedQuotes:1;
104 bool recomputeSyntax:1;
105 bool cursorFollowsView:1;
107 // bool lineNumbers:1;
112 void UnregisterClass_EditBox()
115 for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
124 extern int __ecereVMethodID_class_OnFree;
127 static class ArrayImpl
134 public class OldArray
139 void ** array = (void **)((ArrayImpl)this).array;
140 if(type.type == normalClass || type.type == noHeadClass)
142 for(c = 0; c<size; c++)
143 ((void (*)(void *, void *))(void *)type._vTbl[__ecereVMethodID_class_OnFree])(type, array[c]);
145 // TODO: Call OnFree for structClass
146 delete ((ArrayImpl)this).array;
155 if(((ArrayImpl)this).array)
159 ((ArrayImpl)this).array = renew0 ((ArrayImpl)this).array byte[type.typeSize * value];
162 ((ArrayImpl)this).array = new0 byte[value * type.typeSize];
171 memcpy(((ArrayImpl)this).array, value, type.typeSize * size);
178 public class UndoAction : struct
181 subclass(UndoAction) type;
184 virtual void Undo(void * data) { type.Undo(this, data); }
185 virtual void Redo(void * data) { type.Redo(this, data); }
187 virtual void Print(void * data) { type.Print(this, data); }
191 if(((Class)type).Destructor)
192 ((void (*)(void *))((Class)type).Destructor)(this);
196 public class UndoBuffer
198 Array<UndoAction> actions { size = 8 };
217 bool continued = true;
218 while(curAction > 0 && continued)
220 UndoAction action = actions[--curAction];
224 /*Print("Undoing: ");
225 action.Print(data);*/
230 continued = curAction > 0 && actions[curAction-1].continued;
236 bool continued = true;
237 while(curAction < count && continued)
239 UndoAction action = actions[curAction];
240 continued = action.continued;
246 /*Print("Redoing: ");
247 action.Print(data);*/
256 void Record(UndoAction action)
258 if(!dontRecord && !insideRedo)
260 if(curAction < count)
263 for(c = curAction; c < count; c++)
269 if(count >= actions.size)
270 actions.size += actions.size / 2;
273 /*Print("Recording: ");
274 action.Print(data);*/
278 if(!firstEvent && count > 0)
279 actions[count-1].continued = true;
282 actions[count++] = action;
285 if(actions.size > count + count / 2 && count + count / 2 >= 8)
286 actions.size = count + count / 2;
293 static class AddCharAction : UndoAction
297 int addedSpaces, addedTabs;
298 type = class(AddCharAction);
300 void Undo(EditBox editBox)
302 editBox.GoToPosition(null, (ch == '\n') ? (y + 1) : y, (ch == '\n') ? 0 : (x + 1));
304 if(addedTabs || addedSpaces)
305 editBox.DelCh(editBox.line, y, x - (addedSpaces + addedTabs), editBox.line, y, x, false);
306 editBox.UpdateDirty();
309 void Redo(EditBox editBox)
311 editBox.GoToPosition(null, y, x);
313 editBox.UpdateDirty();
316 void Print(EditBox editBox)
318 PrintLn("AddChar: y = ", y, "x = ", x, ", ch = ", ch, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
323 static class AddTextAction : UndoAction
327 int addedSpaces, addedTabs;
328 type = class(AddTextAction);
331 void Print(EditBox editBox)
333 PrintLn("AddText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs);
341 void Undo(EditBox editBox)
346 editBox.GoToPosition(null, y1, x1);
348 l2 = editBox.lines.first;
349 for(c = 0; c < y2 && l2; c++, l2 = l2.next);
351 editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
352 if(addedTabs || addedSpaces)
353 editBox.DelCh(editBox.line, y1, x1 - (addedSpaces + addedTabs), editBox.line, y1, x1, false);
355 editBox.SetViewToCursor(true);
359 void Redo(EditBox editBox)
361 editBox.GoToPosition(null, y1, x1);
362 editBox.PutS(string);
366 static class DelTextAction : UndoAction
370 bool placeAfter, noHighlight;
372 type = class(DelTextAction);
375 void Print(EditBox editBox)
377 PrintLn("DelText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", string = ", string, ", addedSpaces = ", addedSpaces, ", placeAfter = ", placeAfter);
380 void Undo(EditBox editBox)
382 editBox.GoToPosition(null, y1, x1);
383 editBox.PutS(string);
387 editBox.GoToPosition(null, y1, x1);
393 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
394 //editBox.SetViewToCursor(true);
397 editBox.DelCh(editBox.line, y1, x1 - addedSpaces, editBox.line, y1, x1, false);
406 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
407 //editBox.SetViewToCursor(true);
410 editBox.DelCh(editBox.selLine, y1, x1 - addedSpaces, editBox.selLine, y1, x1, false);
414 void Redo(EditBox editBox)
419 editBox.GoToPosition(null, y1, x1);
422 l2 = editBox.lines.first;
423 for(c = 0; c < y2 && l2; c++, l2 = l2.next);
425 editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
426 editBox.SetViewToCursor(true);
436 static class ReplaceTextAction : UndoAction
438 int y1, x1, y2, x2, y3, x3;
442 int addedSpaces, addedTabs;
444 type = class(ReplaceTextAction);
447 void Print(EditBox editBox)
449 PrintLn("ReplaceText: y1 = ", y1, "x1 = ", x1, ", y2 = ", y2, ", x2 = ", x2, ", y3 = ", y3, ", x3 = ", x3, ", oldString = ", oldString, ", newString = ", newString, ", addedSpaces = ", addedSpaces, ", addedTabs = ", addedTabs, ", placeAfter = ", placeAfter);
452 void Undo(EditBox editBox)
457 editBox.GoToPosition(null, y1, x1);
459 l3 = editBox.lines.first;
460 for(c = 0; c < y3 && l3; c++, l3 = l3.next);
462 editBox.DelCh(l1, y1, x1, l3, y3, x3, true);
464 editBox.PutS(oldString);
465 if(addedSpaces || addedTabs)
466 editBox.DelCh(l1, y1, x1 - (addedSpaces + addedTabs), l1, y1, x1, false);
468 //editBox.PutS(oldString);
471 editBox.GoToPosition(null, y1, x1);
474 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
480 { int c; editBox.selLine = editBox.lines.first; for(c = 0; c < editBox.selY && editBox.selLine; c++, editBox.selLine = editBox.selLine.next); }
484 void Redo(EditBox editBox)
489 editBox.GoToPosition(null, y1, x1);
491 l2 = editBox.lines.first;
492 for(c = 0; c < y2 && l2; c++, l2 = l2.next);
494 editBox.DelCh(l1, y1, x1, l2, y2, x2, true);
496 editBox.PutS(newString);
506 static class MoveTextAction : UndoAction
508 int fy1, fx1, fy2, fx2;
510 type = class(MoveTextAction);
512 void Undo(EditBox editBox)
517 void Redo(EditBox editBox)
523 public class EditLine : struct
535 // Only works for single line edit for now...
536 EditBox editBox = this.editBox;
540 get { return this ? buffer : null; }
542 property EditLine prev { get { return this ? prev : null; } };
543 property EditLine next { get { return this ? next : null; } };
544 property int count { get { return this ? count : 0; } };
551 // This makes sure the buffer always contains at least count characters
552 // Keeps a count/2 pad for avoiding always reallocating memory.
553 bool AdjustBuffer(int count)
561 newSize = (count + (count >> 1));
563 // Shrink down the buffer
566 buffer = new char[newSize];
567 if(!buffer) return false;
571 CopyBytes(buffer, this.buffer, count);
574 this.buffer = buffer;
578 // Increase the buffer
579 else if(size < count)
581 buffer = new char[newSize];
582 if(!buffer) return false;
586 CopyBytes(buffer, this.buffer, this.count + 1); // size);
589 this.buffer = buffer;
598 public struct BufferLocation
603 void AdjustDelete(BufferLocation start, BufferLocation end)
605 // Location is before, nothing to do
606 if(y < start.y || (y == start.y && x < start.x))
608 // Location is inside deleted bytes, point to the start
609 if((y >= start.y && (y > start.y || x >= start.x)) &&
610 (y >= end.y && (y > end.y || x >= end.x)))
615 // Location is on another line
617 y -= end.y - start.y;
618 // Location is the last touched line
625 //if(start.line == end.line)
626 x -= end.x - start.x;
635 // Assuming no carriage return before first character ???? fixed?
636 void AdjustAdd(BufferLocation start, BufferLocation end)
638 int numLines = end.y - start.y;
645 if(x > start.x || (x == start.x /*&& (numLines ? true : false)*/))
648 for(c = 0, line = start.line; c<numLines; c++)
651 //x += numLines ? end.x : (end.x - start.x);
652 x += end.x - start.x;
659 public enum EditBoxFindResult { notFound, found, wrapped };
661 static char * keyWords1[] =
664 "return","break","continue","default","switch","case","if","else","for","while", "do","long","short",
665 "void", "char","int","float","double","signed","unsigned","static", "extern", "struct", "union", "typedef","enum",
667 "#include", "#define", "#pragma", "#if", "#else", "#elif", "#ifdef", "#ifndef", "#endif", "#undef", "#line",
668 "__attribute__", "__stdcall", "_stdcall",
669 "__declspec", "goto",
670 "inline", "__inline__", "_inline", "__inline", "__typeof","__extension__",
671 "asm", "__asm", "_asm", "volatile", "#cpu", "__stdcall__",
672 "__restrict__", "__restrict", "restrict",
675 "class", "private", "public",
677 "delete", "new", "new0", "renew", "renew0", "define",
680 "dllexport", "dllimport", "stdcall",
681 "subclass", "__on_register_module", "namespace", "using",
682 "typed_object", "any_object", "incref", "register", "watch", "stopwatching", "firewatchers", "watchable", "class_designer",
683 "class_fixed", "class_no_expansion", "isset", "class_default_property", "property_category", "class_data", "class_property",
684 "virtual", "thisclass","unichar", "dbtable", "dbindex", "database_open", "dbfield",
687 "uint", "uint32", "uint16", "uint64", "bool", "byte", "int64", "uintptr", "intptr", "intsize", "uintsize",
690 "this", "true", "false", "null", "value",
699 static char * keyWords2[] =
701 "defined", "warning", null
704 static char ** keyWords[] = { keyWords1, keyWords2 };
705 #define NUM_KEYWORD_GROUPS (sizeof(keyWords) / sizeof(char **))
706 //static int * keyLen[NUM_KEYWORD_GROUPS];
707 static int keyLen[NUM_KEYWORD_GROUPS][sizeof(keyWords1)];
709 static char searchString[1025], replaceString[1025];
710 static bool matchCase = false, wholeWord = false, searchUp = false;
712 static GoToDialog goToDialog
714 autoCreate = false, isModal = true, text = $"Go To"
717 public class EditBox : CommonControl
719 class_property(icon) = "<:ecere>controls/editBox.png";
723 virtual bool Window::NotifyModified(EditBox editBox);
724 virtual void Window::NotifyCaretMove(EditBox editBox, int line, int charPos);
725 virtual void Window::NotifyUpdate(EditBox editBox);
726 virtual bool Window::NotifyDoubleClick(EditBox editBox, EditLine line, Modifiers mods);
727 virtual void Window::NotifyOvrToggle(EditBox editBox, bool overwrite);
728 virtual bool Window::NotifyKeyDown(EditBox editBox, Key key, unichar ch);
730 virtual bool Window::NotifyCharsAdded(EditBox editBox, BufferLocation before, BufferLocation after, bool pasteOperation);
731 virtual bool Window::NotifyCharsDeleted(EditBox editBox, BufferLocation beforeLoc, BufferLocation after, bool pasteOperation);
732 virtual bool Window::NotifyDropped(EditBox editBox, int x, int y);
734 virtual bool Window::NotifyUnsetModified(EditBox editBox);
736 // Why was this commented out?
737 // It is required otherwise updating font property from property sheet doesn't immediately reflect in form designer,
738 // and the scrollArea property isn't compared properly either.
742 if(font) ComputeFont();
743 SetInitSize(initSize);
749 style.vScroll = true;
755 style.hScroll = true;
759 property bool textHorzScroll { property_category $"Behavior" set { style.hScroll = value; } get { return style.hScroll; } }; // Should cut the text on set to false
760 property bool textVertScroll { property_category $"Behavior" set { style.vScroll = value; } get { return style.vScroll; } };
761 property bool readOnly
763 property_category $"Behavior"
766 style.readOnly = value;
767 itemEditCut.disabled = value || !selection;
768 itemEditDelete.disabled = value || !selection;
769 itemEditPaste.disabled = value;
771 get { return style.readOnly; }
773 property bool multiLine { property_category $"Behavior" set { style.multiLine = value; } get { return style.multiLine; } };
774 property bool freeCaret { property_category $"Behavior" set { style.freeCaret = value; } get { return style.freeCaret; } };
775 property bool tabKey { property_category $"Behavior" set { style.tabKey = value; } get { return style.tabKey; } };
776 property int tabSize { property_category $"Behavior" set { tabSize = value; } get { return tabSize; } };
777 property bool tabSelection { property_category $"Behavior" set { style.tabSel = value; if(value) style.tabKey = true; } get { return style.tabSel; } };
778 property bool smartHome { property_category $"Behavior" set { style.smartHome = value; } get { return style.smartHome; } };
779 property bool autoEmpty { property_category $"Behavior" set { style.autoEmpty = value; } get { return style.autoEmpty; } };
780 property bool noCaret { property_category $"Behavior" set { style.noCaret = value; if(value) { style.readOnly = true; style.stuckCaret = true; } } get { return style.noCaret; } };
781 property int maxLineSize { property_category $"Behavior" set { maxLineSize = value; } get { return maxLineSize; } };
782 property int maxNumLines { property_category $"Behavior" set { maxLines = value; } get { return maxLines; } };
783 property bool useTab { property_category $"Behavior" set { style.useTab = value; itemEditInsertTab.checked = value; } get { return style.useTab; } };
784 property bool syntaxHighlighting { property_category $"Appearance" set { style.syntax = value; } get { return style.syntax; } };
785 property bool noSelect { property_category $"Behavior" set { style.noSelect = value; } get { return style.noSelect; } };
786 property bool allCaps { property_category $"Behavior" set { style.allCaps = value; } get { return style.allCaps; } };
787 property bool autoSize { property_category $"Behavior" set { style.autoSize = value; } get { return style.autoSize; } };
788 property bool wrap { set { style.wrap = value; Update(null); } get { return style.wrap; } };
789 //property bool lineNumbers { set { style.lineNumbers = value; } get { return style.lineNumbers; } };
790 property int numLines { get { return this ? lineCount : 0; } };
791 property int lineNumber { get { return y; } }; // TODO: Change to property of EditLine this.line.number
792 property int column { get { return col; } }; // TODO: Add Set
793 property int charPos { get { return x; } }; // TODO: Add Set
794 property EditLine firstLine { get { return lines.first; } }; // Change these to a List<EditLine>... (this.lines[10].text)
795 property EditLine lastLine { get { return lines.last; } };
796 property EditLine line { get { return this.line; } }; // TODO: Add Set this.line = this.lines[10]
797 property char * contents
799 property_category $"Data"
804 undoBuffer.dontRecord++;
806 DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
809 //SetViewToCursor(true);
812 undoBuffer.dontRecord--;
818 char * buffer = null;
821 /* Can't implement this right now because of memory leak... Need string reference counting...
828 for(line = lines.first; line; line = line.next)
829 len += strlen(line.buffer);
831 buffer = new char[len+1];
833 for(line = lines.first; line; line = line.next)
835 int lineLen = strlen(line.buffer);
836 memcpy(buffer + len, line.buffer, lineLen);
842 buffer = this.line ? this.line.buffer : null;
847 property bool overwrite { get { return overwrite; } };
848 property bool caretFollowsScrolling { get { return style.cursorFollowsView; } set { style.cursorFollowsView = value; } }
850 property char * multiLineContents
854 char * buffer = null;
861 for(line = lines.first; line; line = line.next)
862 len += strlen(line.buffer)+1;
864 buffer = new char[len+1];
866 for(line = lines.first; line; line = line.next)
868 int lineLen = strlen(line.buffer);
869 memcpy(buffer + len, line.buffer, lineLen);
871 if(line.next) buffer[len++] = '\n';
884 return this.line.buffer;
889 void SetLineText(char * text)
893 EditLine_SetText(this.line, text);
897 property Color selectionColor { set { selectionColor = value; } get { return selectionColor; } isset { return selectionColor ? true : false; } };
898 property Color selectionText { set { selectionText = value; } get { return selectionText; } isset { return selectionText ? true : false; } };
899 property SyntaxColorScheme syntaxColorScheme { set { delete colorScheme; colorScheme = value; incref colorScheme; } }
900 property bool recordUndoEvent { set { undoBuffer.recordAsOne = value; undoBuffer.firstEvent = true; } get { return undoBuffer.recordAsOne; } };
902 // selectionStart.line, selectionStart.column (With Set)
903 // selection.line1, selection.line2, selection.column1, selection.column2 (Read only)
917 // Position of Caret (Not necessarily displayed position)
920 // Position of beginning of block (Block ends at (x,y))
922 // line is line at carret, selLine is line at beginning of block
923 EditLine line, selLine, dropLine;
924 // Mouse selection Moving virtual caret
929 // ViewX is x offset in pixels, ViewY is y offset in lines
931 // viewLine is first displayed line
934 // start and end of area to redraw
937 // MaxLine is the longest line
939 // MaxLength is the longest line's length
942 // MouseSelect is true once button is pressed, overwrite is true if mode is on
943 bool mouseSelect, mouseMove, overwrite, wordSelect;
944 // Timer is used for mouse selection scrolling
947 window = this, delay = 0.1;
956 OnMouseMove(mouseX, mouseY, -1);
961 // (mouseX,mouseY) is the position of the mouse in the edit box
966 void (* FontExtent)(Display display, Font font, char * text, int len, int * width, int * height);
969 bool rightButtonDown;
972 UndoBuffer undoBuffer { data = this };
974 ColorAlpha selectionColor, selectionText;
975 SyntaxColorScheme colorScheme { };
980 Menu editMenu { menu, $"Edit", e };
983 editMenu, $"Cut\tCtrl+X", t, disabled = true;
985 bool NotifySelect(MenuItem item, Modifiers mods)
987 if(!(style.readOnly))
992 MenuItem itemEditCopy
994 editMenu, $"Copy\tCtrl+C", c, disabled = true;
996 bool NotifySelect(MenuItem item, Modifiers mods)
1002 MenuItem itemEditPaste
1004 editMenu, $"Paste\tCtrl+V", p;
1006 bool NotifySelect(MenuItem item, Modifiers mods)
1008 if(!(style.readOnly))
1013 MenuItem itemEditDelete
1015 editMenu, $"Delete\tDel", d, disabled = true;
1017 bool NotifySelect(MenuItem item, Modifiers mods)
1019 if(!(style.readOnly))
1024 MenuDivider { editMenu };
1025 MenuItem itemEditSelectAll
1027 editMenu, $"Select All\tCtrl+A", a;
1029 bool NotifySelect(MenuItem item, Modifiers mods)
1035 MenuDivider { editMenu };
1036 MenuItem itemEditUndo
1038 editMenu, $"Undo\tCtrl+Z", u;
1041 bool NotifySelect(MenuItem item, Modifiers mods)
1047 MenuItem itemEditRedo
1049 editMenu, $"Redo\tCtrl+Y", o;
1052 bool NotifySelect(MenuItem item, Modifiers mods)
1058 MenuDivider { editMenu };
1061 editMenu, $"Find Previous\tShift-F3", e, shiftF3;
1063 bool NotifySelect(MenuItem item, Modifiers mods)
1066 Find(searchString, wholeWord, matchCase, false);
1068 itemEditFind.NotifySelect(this, item, mods);
1074 editMenu, $"Find Next\tF3", n, f3;
1076 bool NotifySelect(MenuItem item, Modifiers mods)
1079 Find(searchString, wholeWord, matchCase, true);
1081 itemEditFind.NotifySelect(this, item, mods);
1085 MenuItem itemEditFind
1087 editMenu, $"Find...\tCtrl+F", f, ctrlF;
1089 bool NotifySelect(MenuItem item, Modifiers mods)
1093 editBox = this, master = master, isModal = true, searchString = searchString, matchCase = matchCase, wholeWord = wholeWord,
1094 searchUp = searchUp;
1097 // Fix dialog from above shouldn't be visible
1098 // void NotifyDestroyed(FindDialog dialog, DialogResult result)
1099 void NotifyDestroyed(Window window, DialogResult result)
1101 FindDialog dialog = (FindDialog) window;
1102 searchUp = dialog.searchUp;
1103 strcpy(searchString, dialog.searchString);
1104 matchCase = dialog.matchCase;
1105 wholeWord = dialog.wholeWord;
1114 editMenu, $"Replace...\tCtrl+R", r, ctrlR;
1116 bool NotifySelect(MenuItem item, Modifiers mods)
1118 ReplaceDialog dialog
1122 searchString = searchString,
1123 replaceString = replaceString,
1124 matchCase = matchCase,
1125 wholeWord = wholeWord,
1129 // void NotifyDestroyed(ReplaceDialog dialog, DialogResult result)
1130 void NotifyDestroyed(Window window, DialogResult result)
1132 ReplaceDialog dialog = (ReplaceDialog)window;
1133 char * replace = dialog.replaceString;
1135 strcpy(replaceString, replace);
1136 strcpy(searchString, dialog.searchString);
1137 matchCase = dialog.matchCase;
1138 wholeWord = dialog.wholeWord;
1145 MenuDivider { editMenu };
1148 editMenu, $"Go To...\tCtrl+G", g, ctrlG;
1150 bool NotifySelect(MenuItem item, Modifiers mods)
1152 goToDialog.editBox = this;
1153 goToDialog.master = master;
1154 goToDialog.Create();
1158 MenuDivider { editMenu };
1159 MenuItem itemEditInsertTab
1161 editMenu, $"Insert Tabs", i, checkable = true;
1163 bool NotifySelect(MenuItem item, Modifiers mods)
1165 style.useTab = item.checked;
1171 snapVertScroll = true;
1172 snapHorzScroll = true;
1176 static bool syntaxInit = false;
1181 for(g = 0; g<NUM_KEYWORD_GROUPS; g++)
1183 for(c = 0; keyWords[g][c]; c++);
1184 //keyLen[g] = new int[c];
1185 for(c = 0; keyWords[g][c]; c++)
1187 keyLen[g][c] = strlen(keyWords[g][c]);
1191 colorScheme.commentColor = dimGray;
1192 colorScheme.charLiteralColor = crimson;
1193 colorScheme.stringLiteralColor = crimson;
1194 colorScheme.preprocessorColor = green;
1195 colorScheme.numberColor = teal;
1196 colorScheme.keywordColors = [ blue, blue ];
1199 FontExtent = Display::FontExtent;
1201 lines.offset = (uint)&((EditLine)0).prev;
1203 style = EditBoxBits { hScroll = true };
1205 // cursor = guiApp.GetCursor(IBeam);
1207 // Default Properties
1209 maxLineSize = 1024;*/
1212 maxLineSize = MAXINT;
1217 mouseSelect = this.mouseMove = false;
1220 x = selX = selY = 0;
1223 line = selLine = null;
1229 endY = clientSize.h;
1232 // Default space to 8 in case window is not constructed
1235 undoBuffer.dontRecord++;
1237 undoBuffer.dontRecord--;
1239 viewLine = lines.first;
1240 style.recomputeSyntax = true;
1246 UpdateCaretPosition(true);
1252 lines.Free(EditLine::Free);
1255 void FlushBuffer(Surface surface, EditLine line, int wc, int * renderStart, int * x, int y, int numSpaces, bool drawSpaces, Box box)
1257 int count = wc - *renderStart;
1260 if(y + space.h >= box.top && y <= box.bottom)
1266 //FontExtent(display, font, line.buffer + *renderStart, count, &w, null);
1267 surface.TextFont(font);
1268 surface.TextExtent(line.buffer + *renderStart, count, &w, null);
1269 if(*x + w + XOFFSET > 0)
1270 surface.WriteText(XOFFSET + *x,y, line.buffer + *renderStart, count);
1275 w = numSpaces; // * space.w;
1276 if(*x + w + XOFFSET > 0 && (drawSpaces || surface.GetTextOpacity()))
1277 surface.Area(XOFFSET + *x - 1, y, XOFFSET + *x + w, y + space.h-1);
1278 // WHATS UP WITH THIS... surface.Area(XOFFSET + *x, y, XOFFSET + *x + w, y + space.h-1);
1286 int CheckColors(EditLine line, int wc, bool selection, int selX, int editX, bool *selected,
1287 Color selectionForeground, Color selectionBackground, Color textColor, Color *foreground, Color *background, bool *opacity, bool *overwrite)
1293 if((wc == selX && line == selLine) || (wc == editX && line == this.line))
1297 *foreground = (*selected) ? selectionForeground : textColor;
1298 *background = selectionBackground;
1299 *opacity = *selected;
1306 if(this.overwrite && active)
1308 if((style.stuckCaret && wc == line.count && !line.next) ||
1309 (!mouseMove && line == this.line && wc == editX))
1318 void FigureStartSyntaxStates(EditLine firstLine, bool reset)
1322 bool inMultiLineComment = reset ? false : style.inMultiLineComment;
1323 bool wasInMultiLine = reset ? false : style.wasInMultiLine;
1324 bool inString = false;
1325 bool inQuotes = false;
1326 bool inPrep = reset ? false : style.inPrep;
1327 bool inSingleLineComment = false;
1328 bool escaped = reset ? false : style.escaped;
1329 bool continuedSingleLineComment = reset ? false : style.continuedSingleLineComment;
1330 bool continuedString = reset ? false : style.continuedString;
1331 bool continuedQuotes = reset ? false : style.continuedQuotes;
1333 EditLine line = reset ? lines.first : firstLine;
1334 // int maxBackUp = 1000, c;
1335 // for(c = 0, line = viewLine; c < maxBackUp && line && line.prev; line = line.prev);
1336 for(; line != viewLine; line = line.next)
1338 char * text = line.buffer;
1341 bool lastWasStar = false;
1342 bool firstWord = true;
1343 if(!escaped) inPrep = false;
1344 inSingleLineComment = continuedSingleLineComment;
1346 inString = continuedString;
1347 inQuotes = continuedQuotes;
1350 for(c = 0; (ch = text[c]); c++)
1352 bool wasEscaped = escaped;
1353 bool backLastWasStar = lastWasStar;
1354 bool backWasInMultiLine = wasInMultiLine;
1356 lastWasStar = false;
1357 wasInMultiLine = inMultiLineComment;
1360 if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
1362 if(text[c+1] == '/')
1364 inSingleLineComment = true;
1366 else if(text[c+1] == '*')
1368 inMultiLineComment = true;
1371 else if(backLastWasStar)
1372 inMultiLineComment = false;
1376 if(backWasInMultiLine) lastWasStar = true;
1378 else if(ch == '\"' && !inSingleLineComment && !inMultiLineComment && !inQuotes)
1380 if(inString && !wasEscaped)
1389 else if(ch == '\'' && !inSingleLineComment && !inMultiLineComment && !inString)
1391 if(inQuotes && !wasEscaped)
1403 else if(ch == '#' && !inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1410 else if(ch != ' ' && ch != '\t')
1413 if(line.count && line.text[line.count - 1] == '\\')
1415 continuedSingleLineComment = inSingleLineComment;
1416 continuedString = inString;
1417 continuedQuotes = inQuotes;
1421 continuedSingleLineComment = false;
1422 continuedString = false;
1423 continuedQuotes = false;
1427 style.continuedSingleLineComment = continuedSingleLineComment;
1428 style.continuedString = continuedString;
1429 style.continuedQuotes = continuedQuotes;
1430 style.inMultiLineComment = inMultiLineComment;
1431 style.wasInMultiLine = wasInMultiLine;
1432 style.inPrep = inPrep;
1433 style.escaped = escaped;
1437 /*void OnDrawOverChildren(Surface surface)
1439 if(style.lineNumbers)
1441 int currentLineNumber = this.viewY + 1;
1444 for(i = 0; i * space.h < box.height; i++)
1446 // ********* LINE NUMBERING *********
1447 surface.SetForeground(Color{60, 60, 60});
1448 //Highlight current line
1449 if(this.caretY / space.h == currentLineNumber - 1)
1450 surface.SetBackground(Color{220, 220, 220});
1452 surface.SetBackground(Color{230, 230, 230});
1453 surface.textOpacity = true;
1455 sprintf(lineText,"%5u ", currentLineNumber % 100000);
1456 if(currentLineNumber > this.lineCount)
1457 surface.WriteText(0,i*space.h+1," ",6);
1459 surface.WriteText(0,i*space.h+1,lineText,6);
1461 currentLineNumber++;
1466 void OnRedraw(Surface surface)
1470 bool selected = false, selection = true;
1472 Color selectionBackground = selectionColor ? selectionColor : SELECTION_COLOR;
1473 Color selectionForeground = selectionText ? selectionText : SELECTION_TEXT;
1474 Color defaultTextColor = property::foreground;
1477 int maxW = clientSize.w;
1479 Color foreground, background;
1482 // Overwrite Caret Stuff
1483 bool overWrite = false;
1484 int overWriteX, overWriteY;
1487 // ****** SYNTAX STATES ******
1488 bool inMultiLineComment = style.inMultiLineComment;
1489 bool inString = false;
1490 bool inQuotes = false;
1491 bool inPrep = style.inPrep;
1492 bool inSingleLineComment = false;
1493 bool escaped = style.escaped;
1494 bool continuedSingleLineComment = style.continuedSingleLineComment;
1495 bool continuedString = style.continuedString;
1496 bool continuedQuotes = style.continuedQuotes;
1497 bool wasInMultiLine = style.wasInMultiLine;
1498 // ****** ************* ******
1501 defaultTextColor = Color { 85, 85, 85 };
1502 textColor = defaultTextColor;
1505 Abs(selectionBackground.r - property::background.r) +
1506 Abs(selectionBackground.g - property::background.g) +
1507 Abs(selectionBackground.b - property::background.b) < 92)
1509 selectionBackground = formColor;
1510 selectionForeground = selectionColor ? selectionColor : SELECTION_COLOR;
1513 surface.TextFont(this.font);
1515 surface.SetBackground(GDefaultPalette()[RND_Get(1, 15)]);
1516 surface.Area(0,0,MAXINT,MAXINT);
1521 surface.SetBackground(formColor);
1522 surface.Area(0,0,clientSize.w, clientSize.h);
1525 if(this.selX == this.x && this.selY == this.y)
1535 editX = Min(this.x,this.line.count);
1536 selX = Min(this.selX,this.selLine.count);
1539 selected = (this.selY < this.viewY) ^ (this.y < this.viewY);
1541 foreground = selected ? selectionForeground : textColor;
1542 background = selectionBackground;
1548 surface.SetForeground(foreground);
1549 surface.SetBackground(background);
1550 surface.TextOpacity(opacity);
1552 surface.GetBox(box);
1554 for(line = this.viewLine; line; line = line.next)
1556 int x = -this.viewX;
1560 Color newTextColor = textColor = defaultTextColor;
1561 bool lineComplete = false;
1564 // ****** SYNTAX HIGHLIGHTING ******
1565 bool lastWasStar = false;
1566 bool trailingSpace = false;
1567 bool firstWord = true;
1568 if(!escaped) inPrep = false;
1569 inSingleLineComment = continuedSingleLineComment;
1571 inString = continuedString;
1572 inQuotes = continuedQuotes;
1573 // *********************************
1575 /* === DEBUGGING TOOL FOR MAXLINE ===
1577 if(line == this.maxLine)
1579 surface.SetBackground(GREEN|0xFF000000);
1580 surface.TextOpacity(true);
1584 surface.TextOpacity(selected ? true : false);
1585 surface.SetBackground(selected ? SELECTION_COLOR|0xFF000000 : BLACK|0xFF000000);
1589 if(line == this.selLine && line == this.line)
1591 end = Max(line.count, this.x);
1592 end = Max(end, this.selX);
1594 else if(line == this.selLine)
1595 end = Max(line.count, this.selX);
1596 else if(line == this.line)
1597 end = Max(line.count, this.x);
1605 bool spacing = true;
1606 bool cantHaveWords = false;
1613 lineComplete = false;
1616 textColor = newTextColor;
1619 foreground = textColor;
1620 surface.SetForeground(textColor);
1624 for(; c<end && !cantHaveWords;)
1628 bufferLen += wordLen;
1632 /*if(line.count > 4000)
1639 for(; c<line.count; c++)
1641 unichar ch = line.buffer[c];
1642 unichar bf = (wordLen == 1) ? line.buffer[c-1] : 0;
1643 //if(ch == ' ' || ch == '\t' || (wordLen && (ch == '(' || ch == ')' || ch == ';' || ch == ':')) || (wordLen == 1 && line.buffer[c-1] == '('))
1644 if(CharMatchCategories(ch, separators) || /*ch == ' ' ||*/ ch == '\t' ||
1645 (wordLen && !CharMatchCategories(ch, numbers|letters|marks|connector) && ch != '#' /*&& ch != '_'*/) ||
1646 (bf && !CharMatchCategories(bf, numbers|letters|separators|marks|connector) && bf != '#' && bf != '\t' /*&& bf != '_' && bf != ' '*/))
1649 trailingSpace = false;
1655 for(; c<line.count; c++)
1657 unichar ch = line.buffer[c];
1658 if(ch == '\t' || ch == ' ')
1660 cantHaveWords = true;
1661 if(ch == ' ' && c == line.count-1)
1662 trailingSpace = true;
1666 if(ch != ' ' && ch != '\t')
1671 if(c == line.count && c < end)
1680 cantHaveWords = true;
1685 lastWasStar = false;
1691 bool backEscaped = escaped;
1692 bool backLastWasStar = lastWasStar;
1693 bool backInMultiLineComment = inMultiLineComment;
1694 bool backInString = inString;
1695 bool backInQuotes = inQuotes;
1696 bool backInPrep = inPrep;
1697 bool backInSingleLineComment = inSingleLineComment;
1698 bool backWasInMultiLine = wasInMultiLine;
1700 char * word = line.buffer + c - wordLen;
1702 bool wasEscaped = escaped;
1704 lastWasStar = false;
1706 wasInMultiLine = inMultiLineComment;
1708 // Determine Syntax Highlighting
1709 newTextColor = defaultTextColor;
1712 if(inSingleLineComment || inMultiLineComment)
1714 newTextColor = colorScheme.commentColor;
1718 newTextColor = colorScheme.charLiteralColor;
1722 newTextColor = colorScheme.stringLiteralColor;
1726 newTextColor = colorScheme.preprocessorColor;
1728 if(wordLen == 1 && word[0] == '/')
1730 if(!inSingleLineComment && !inMultiLineComment && !inQuotes && !inString)
1734 inSingleLineComment = true;
1735 newTextColor = colorScheme.commentColor;
1737 else if(word[1] == '*')
1739 inMultiLineComment = true;
1740 newTextColor = colorScheme.commentColor;
1743 else if(backLastWasStar)
1744 inMultiLineComment = false;
1746 else if(wordLen == 1 && word[0] == '*')
1748 if(backWasInMultiLine)
1751 else if(!inSingleLineComment && !inMultiLineComment && !inQuotes && wordLen == 1 && word[0] == '\"')
1753 if(inString && !wasEscaped)
1760 newTextColor = colorScheme.stringLiteralColor;
1763 else if(!inSingleLineComment && !inMultiLineComment && !inString && wordLen == 1 && word[0] == '\'')
1765 if(inQuotes && !wasEscaped)
1770 newTextColor = colorScheme.charLiteralColor;
1773 else if(wordLen == 1 && word[0] == '\\')
1778 else if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && (isdigit(word[0]) || (word[0] == '.' && isdigit(word[1]))))
1780 char * dot = strchr(word, '.');
1781 bool isHex = (word[0] == '0' && (word[1] == 'x' || word[1] == 'X'));
1787 exponent = strchr(word, 'p');
1788 if(!exponent) exponent = strchr(word, 'P');
1792 exponent = strchr(word, 'e');
1793 if(!exponent) exponent = strchr(word, 'E');
1795 if(exponent && exponent > word + wordLen) exponent = null;
1796 if(dot && dot > word + wordLen) dot = null;
1797 isReal = dot || exponent;
1799 strtod(word, &s); // strtod() seems to break on hex floats (e.g. 0x23e3p12, 0x1.fp3)
1801 strtol(word, &s, 0);
1807 int gotF = 0, gotL = 0, gotU = 0, gotI = 0;
1810 for(i = 0; valid && i < 5 && (ch = s[i]) && (isalnum(ch) || ch == '_'); i++)
1814 case 'f': case 'F': gotF++; if(gotF > 1 || !isReal) valid = false; break;
1817 if(gotL > 2 || isReal || (gotL == 2 && (s[i-1] != ch)))
1820 case 'u': case 'U': gotU++; if(gotU > 1 || isReal) valid = false; break;
1821 case 'i': case 'I': case 'j': case 'J': gotI++; if(gotI > 1) valid = false; break;
1822 default: valid = false;
1826 // Don't highlight numbers with too many decimal points
1827 if(s[0] == '.' && isdigit(s[1]))
1830 while(s[0] == '.' && isdigit(s[1]))
1832 int newWordLen = s - word;
1833 c += newWordLen - wordLen;
1834 wordLen = newWordLen;
1837 newWordLen = s - word;
1838 c += newWordLen - wordLen;
1839 wordLen = newWordLen;
1843 int newWordLen = s + i - word;
1844 newTextColor = colorScheme.numberColor;
1845 c += newWordLen - wordLen;
1846 wordLen = newWordLen;
1848 else if(dot && dot > word && dot < s)
1849 newTextColor = colorScheme.numberColor;
1854 if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment && word[0] == '#')
1859 newTextColor = colorScheme.preprocessorColor;
1862 if(!inQuotes && !inString && !inMultiLineComment && !inSingleLineComment)
1864 for(g = 0; g < ((inPrep && word[0] != '#') ? 2 : 1); g++)
1866 char ** keys = keyWords[g];
1867 int * len = keyLen[g];
1868 for(ccc = 0; keys[ccc]; ccc++)
1870 if(len[ccc] == wordLen && !strncmp(keys[ccc], word, wordLen))
1872 newTextColor = colorScheme.keywordColors[g];
1881 // If highlighting for this word is different, break
1882 if(newTextColor != textColor)
1886 // Better solution than going back?
1889 // Reset syntax flags
1890 escaped = backEscaped;
1891 lastWasStar = backLastWasStar;
1892 inMultiLineComment = backInMultiLineComment;
1893 inString = backInString;
1894 inQuotes = backInQuotes;
1895 inPrep = backInPrep;
1896 inSingleLineComment = backInSingleLineComment;
1897 wasInMultiLine = backWasInMultiLine;
1902 textColor = newTextColor;
1905 foreground = textColor;
1906 surface.SetForeground(textColor);
1913 // If we're not breaking, this can't be rendered as spacing anymore
1916 // Do word wrapping here
1922 FontExtent(display, font, line.buffer + start, bufferLen + wordLen, &tw, null);
1927 w += numSpaces * space.w;
1929 if(x + viewX + w > maxW)
1931 // Avoid an endless loop until we fix wrapping
1932 if(c - wordLen > start)
1935 lineComplete = true;
1941 bufferLen += wordLen;
1946 int renderStart = start;
1948 bool flagTrailingSpace = false;
1952 // Render checking if we need to split because of selection or to find where to draw insert caret
1953 for(wc = start; wc < start + bufferLen; wc++)
1955 flush = CheckColors(line, wc, selection, selX, editX, &selected, selectionForeground,
1956 selectionBackground, textColor, &foreground, &background, &opacity, &overWrite);
1957 if(overWrite == true)
1959 overWriteCh = (wc < line.count) ? line.buffer[wc] : ' ';
1960 if(overWriteCh == '\t') overWriteCh = ' ';
1965 flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
1966 if(flagTrailingSpace) surface.SetBackground(red);
1967 FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
1968 if(overWrite == true)
1976 surface.TextOpacity(opacity);
1977 surface.SetBackground(background);
1978 surface.SetForeground(foreground);
1985 if(wc < line.count && line.buffer[wc] == '\t')
1987 numSpaces += (tabSize * space.w) - ((x + numSpaces + viewX) % (tabSize * space.w));
1991 numSpaces += space.w;
1995 flagTrailingSpace = numSpaces && trailingSpace && style.syntax && start + bufferLen == line.count && line != this.line;
1996 if(flagTrailingSpace) surface.SetBackground(red);
1997 FlushBuffer(surface, line, wc, &renderStart, &x, y, numSpaces, flagTrailingSpace, box);
2002 if(CheckColors(line, c, selection, selX, editX, &selected, selectionForeground,
2003 selectionBackground, textColor, &foreground, &background, &opacity, &overWrite))
2005 if(overWrite == true)
2012 surface.TextOpacity(opacity);
2013 surface.SetBackground(background);
2014 surface.SetForeground(foreground);
2017 if(style.freeCaret && selected)
2019 surface.SetBackground(selectionBackground);
2020 surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
2021 // TEST: surface.Area(x + XOFFSET,y,clientSize.w-1,y+this.space.h-1);
2026 if(style.freeCaret && selected)
2028 surface.SetBackground(selectionBackground);
2029 surface.Area(x + XOFFSET - 1,y,clientSize.w-1,y+this.space.h-1);
2032 if(line.count && line.text[line.count - 1] == '\\')
2034 continuedSingleLineComment = inSingleLineComment;
2035 continuedString = inString;
2036 continuedQuotes = inQuotes;
2040 continuedSingleLineComment = false;
2041 continuedString = false;
2042 continuedQuotes = false;
2046 if(y > box.bottom) // >=clientSize.h)
2052 surface.TextOpacity(true);
2053 surface.SetForeground(black);
2054 surface.SetBackground(Color {255,255,85});
2055 surface.WriteText(XOFFSET + overWriteX,overWriteY, &overWriteCh, 1);
2059 void FixScrollArea()
2061 if(style.hScroll || style.vScroll)
2063 int width = maxLength + XOFFSET;
2064 int height = lineCount * space.h;
2065 if(style.freeCaret && line)
2070 width = Max(line.length + (x - line.count) * space.w, maxLength + XOFFSET);
2074 if(selX > selLine.count)
2075 width = Max(selLine.length + (selX - selLine.count) * space.w, maxLength + XOFFSET);
2080 SetScrollLineStep(8, space.h);
2081 SetScrollArea(width, height, true);
2085 void ComputeLength(EditLine line)
2092 for(c = 0; c < line.count; )
2101 for(len = 0; c < line.count; c += numBytes)
2103 ch = line.buffer[c];
2104 numBytes = UTF8_NUM_BYTES(ch);
2106 if(ch == ' ' || ch == '\t')
2113 if(!len && ch == ' ')
2118 else if(!len && ch == '\t')
2120 w = (tabSize * space.w) - (x % (tabSize * space.w));
2124 FontExtent(display, font, line.buffer + start, len, &w, null);
2135 if(line.length > this.maxLength)
2137 this.maxLine = line;
2138 this.maxLength = line.length;
2140 if(style.autoSize) AutoSize();
2149 this.maxLine = null;
2151 for(line = lines.first; line; line = line.next)
2153 if(line.length > this.maxLength)
2155 this.maxLength = line.length;
2156 this.maxLine = line;
2160 if(style.autoSize) AutoSize();
2165 if(this.selY != this.y)
2167 else if(this.selX != this.x) // commented out to erase caret: if(this.selX != this.x)
2171 void ComputeColumn()
2173 // Adjust new edit X position according to tabs
2174 int c, position = 0;
2177 for(c = 0; c<this.line.count && c<this.x && (ch = UTF8_GET_CHAR(this.line.buffer + c, nb)); c+= nb)
2179 // TODO: MIGHT WANT TO RETHINK WHAT COLUMN SHOULD BE REGARDING TABS
2181 position += this.tabSize - (position % this.tabSize);
2185 position += this.x - c;
2186 this.col = position;
2189 void DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter)
2191 _DelCh(l1, y1, c1, l2, y2, c2, placeAfter, true, null);
2194 bool HasCommentOrEscape(EditLine line)
2196 bool hadComment = strstr(line.buffer, "/*") || strstr(line.buffer, "*/");
2201 for(c = line.count-1; c >= 0; c--)
2203 char ch = line.buffer[c];
2209 else //if(ch != ' ' && ch != '\t')
2216 int _DelCh(EditLine l1, int y1, int c1, EditLine l2, int y2, int c2, bool placeAfter, bool highlight, int * addedSpacesPtr)
2218 EditLine line = l1, next;
2220 int oldCount1, oldCount2;
2221 int y, firstViewY, firstY, firstDropY, firstSelY;
2224 DelTextAction action = null;
2225 bool hadComment = false;
2229 hadComment = HasCommentOrEscape(line);
2231 if(y2 > y1 || c2 > c1)
2233 if(start < l1.count)
2235 while(!UTF8_IS_FIRST(l1.buffer[start]) && start)
2239 oldCount1 = l1.count;
2241 while(c1 < oldCount1)
2243 byte ch = buffer[c1];
2244 if(UTF8_IS_FIRST(ch)) break;
2248 oldCount2 = l2.count;
2250 while(c2 < oldCount2)
2252 byte ch = buffer[c2];
2253 if(UTF8_IS_FIRST(ch)) break;
2258 if(!undoBuffer.dontRecord && (y2 > y1 || c2 > c1))
2263 len = GetText(null, l1, y1, start, l2, y2, c2, false, false);
2264 string = new char[len];
2265 action = DelTextAction { y1 = y1, x1 = start, y2 = y2, x2 = c2, string = string, placeAfter = placeAfter, noHighlight = !highlight };
2266 GetText(string, l1, y1, start, l2, y2, c2, false, false);
2270 //oldCount1 = l1.count;
2271 //oldCount2 = l2.count;
2274 BufferLocation before = { l1,y1,c1}, after = { l2,y2,c2 };
2275 NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
2278 if(c2 > oldCount2) c2 = oldCount2;
2279 if(!(style.freeCaret))
2280 if(c1 > oldCount1) c1 = oldCount1;
2281 newLineCount = c1 + l2.count-c2;
2287 buffer = new char[line.size ? line.size : 1];
2289 buffer = new char[line.size];
2290 // TODO: Better handling of these allocation failures
2291 if(!buffer) return extras;
2292 CopyBytes(buffer,l2.buffer,oldCount1 + 1/*line.count + 1*//*line.size*/);
2297 // TODO: Better handling of these allocation failures
2298 if(!line.AdjustBuffer(newLineCount))
2302 /*if(newLineCount > 4000 || newLineCount < 0)
2303 printf("Warning");*/
2305 line.count = newLineCount;
2307 memmove(l1.buffer+c1, buffer+c2,line.count-c1);
2312 action.addedSpaces = c1-oldCount1;
2314 if(action.addedSpaces > action.x1)
2320 if(addedSpacesPtr) *addedSpacesPtr = c1-oldCount1;
2321 FillBytes(l1.buffer+oldCount1,' ',c1-oldCount1);
2323 line.buffer[line.count] = '\0';
2330 this.dropX -= c2-c1;
2331 this.dropX = Max(this.dropX,0);
2333 this.x = Max(this.x,0);
2340 firstViewY = this.viewY;
2342 firstDropY = this.dropY;
2343 firstSelY = this.selY;
2344 for(line = l1;line;line = next, y++)
2352 if(line == this.viewLine)
2354 if(this.viewLine.next)
2356 this.viewLine = this.viewLine.next;
2358 style.recomputeSyntax = true;
2362 this.viewLine = this.viewLine.prev;
2364 style.recomputeSyntax = true;
2367 else if(y < firstViewY)
2369 if(line == this.line)
2373 this.line = this.line.next;
2374 this.x = this.line.count;
2379 this.line = this.line.prev;
2380 this.x = this.line.count;
2387 if(line == this.dropLine)
2389 if(this.dropLine.next)
2391 this.dropLine = this.dropLine.next;
2392 this.dropX = this.dropLine.count;
2396 this.dropLine = this.dropLine.prev;
2397 this.dropX = this.dropLine.count;
2401 else if(y < firstDropY)
2403 if(line == this.selLine)
2405 if(this.selLine.next)
2407 this.selLine = this.selLine.next;
2408 this.selX = this.selLine.count;
2412 this.selLine = this.selLine.prev;
2413 this.selX = this.selLine.count;
2417 else if(y < firstSelY)
2421 if(line == l2) break;
2425 if(style.syntax && (hadComment || HasCommentOrEscape(this.line)))
2428 style.recomputeSyntax = true;
2433 bool DelSel(int * addedSpacesPtr)
2435 if(this.line != this.selLine || this.x != this.selX)
2437 if(this.selY < this.y)
2439 _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, true, addedSpacesPtr);
2442 this.line = this.selLine;
2444 else if(this.selY > this.y)
2446 _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, true, addedSpacesPtr);
2449 this.selLine = this.line;
2451 else if(this.selX < this.x)
2453 _DelCh(this.selLine, this.selY, this.selX, this.line, this.y, this.x, true, true, addedSpacesPtr);
2456 this.line = this.selLine;
2460 _DelCh(this.line, this.y, this.x, this.selLine, this.selY, this.selX, false, true, addedSpacesPtr);
2463 this.selLine = this.line;
2471 bool AddToLine(char * stringLine, int count, bool LFComing, int * addedSpacesPtr, int * addedTabsPtr)
2473 bool hadComment = false;
2474 // Add the line here
2475 EditLine line = this.line;
2477 // The purpose of this is solely to lock a max number of characters if no HSCROLLING is present
2478 if(!style.hScroll && created)
2480 int endX = (style.freeCaret) ? this.x : Min(this.x, line.count);
2485 // Lock if no place to display.
2488 else if(endX > this.x)
2489 max = Max(this.x, line.count);
2493 for(x = 0, c = 0; c < max+count; )
2498 if(c < Min(this.x, line.count))
2499 string = line.buffer + c;
2502 else if(c < endX + count)
2503 string = stringLine + c - endX;
2505 string = line.buffer + c - endX - count;
2509 w = (tabSize * space.w) - (x % (tabSize * space.w));
2513 numBytes = UTF8_NUM_BYTES(*string);
2514 FontExtent(display, this.font, string, numBytes, &w, null);
2518 if(x >= clientSize.w)
2523 c += numBytes; // - 1;
2529 int addedSpaces = 0;
2532 // Add blank spaces if EES_FREECARET
2533 if(this.x > line.count)
2543 for(c = 0; c<line.count; c++)
2545 if(this.line.buffer[c] == '\t')
2546 position += this.tabSize - (position % this.tabSize);
2550 wantedPosition = position + (this.x - line.count);
2552 // A tab is too much...
2553 if(position + (this.tabSize - (position % this.tabSize)) > wantedPosition)
2554 addedSpaces = wantedPosition - position;
2559 position += this.tabSize - (position % this.tabSize);
2560 // Add as many tabs as needed
2561 addedTabs += (wantedPosition - position) / this.tabSize;
2562 position += (addedTabs-1) * this.tabSize;
2563 // Finish off with spaces
2564 addedSpaces = wantedPosition - position;
2568 addedSpaces = this.x - line.count;
2571 this.x = Min(this.x, line.count);
2573 if(line.count + count + addedSpaces + addedTabs > this.maxLineSize)
2579 hadComment = HasCommentOrEscape(line);
2581 // Adjust the size of the line
2582 if(!line.AdjustBuffer(line.count+count+addedTabs+addedSpaces))
2586 BufferLocation before = { this.line, this.y, this.x }, after = { this.line, this.y, this.x };
2589 memmove(line.buffer+this.x+count, line.buffer+this.x,line.count-this.x);
2590 CopyBytes(line.buffer + this.x + addedTabs + addedSpaces, stringLine, count);
2593 *addedTabsPtr = addedTabs;
2594 FillBytes(line.buffer+line.count,'\t',addedTabs);
2596 if(addedTabs > 4000 || addedTabs < 0)
2599 line.count += addedTabs;
2605 FillBytes(line.buffer+line.count,' ',addedSpaces);
2607 if(addedSpaces > 4000 || addedSpaces < 0)
2610 line.count += addedSpaces;
2611 if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
2613 else if(addedSpacesPtr)
2614 *addedSpacesPtr = 0;
2616 if(count > 4000 || count < 0)
2619 line.count += count;
2620 this.x += count + addedTabs + addedSpaces;
2623 this.selLine = this.line;
2625 line.buffer[line.count] = '\0';
2627 ComputeLength(line);
2632 hasComment = HasCommentOrEscape(line);
2633 if(!undoBuffer.insideRedo)
2635 int backDontRecord = undoBuffer.dontRecord;
2636 undoBuffer.dontRecord = 0;
2637 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
2638 undoBuffer.dontRecord = backDontRecord;
2640 if(style.syntax && (hadComment || hasComment || line != this.line))
2642 style.recomputeSyntax = true;
2650 void Emptyline(EditLine line, int y)
2653 DelCh(line, y, 0, line.next, y+1, 0, true);
2655 DelCh(line, y, 0, line, y, line.count, true);
2658 void GoToEnd(bool deselect)
2662 this.line = this.lines.last;
2663 if(this.y != this.lineCount-1)
2665 else if (this.x != this.line.count)
2666 DirtyLine(this.lineCount-1);
2667 this.y = this.lineCount-1;
2668 this.x = this.line.count;
2675 void GoToHome(bool deselect)
2679 this.line = this.lines.first;
2682 else if (this.x !=0)
2683 DirtyLine(this.lineCount-1);
2692 // Returns true if it needs scrolling
2693 bool FindMouse(int px, int py, int * tx, int * ty, EditLine * tline, bool half)
2699 bool needHScroll = false;
2706 line = this.viewLine ? (void *)this.viewLine.prev : null;
2711 line = (void *)this.lines.first;
2716 py = Min(py, clientSize.h);
2718 py = Min(py, this.lineCount);
2720 for(c = 0, line = this.viewLine; (line != (void *)this.lines.last && c<py); line = line.next, c++)
2726 if( (px >= clientSize.w || px < clientSize.w/2) && this.viewX)
2729 px = Min(px,clientSize.w+this.space.w);
2733 *tx = AdjustXPosition(line, px + viewX, half, null, MAXINT, 0);
2736 if(tline) *tline = line;
2739 // Prevent divide by 0 from non valid this.font
2741 return (y < this.viewY) || needHScroll;
2743 return (y < this.viewY || y >= this.viewY + clientSize.h / this.space.h) || needHScroll;
2747 // Minimal Update Management Functions
2751 this.endY = clientSize.h-1;
2752 // ErrorLog("DirtyAll\n");
2755 void DirtyEnd(int y)
2757 if((y - this.viewY)*this.space.h < this.startY)
2758 this.startY = (y - this.viewY)*this.space.h+ YOFFSET;
2759 this.endY = clientSize.h-1;
2760 //ErrorLog("DirtyEnd %d\n", y);
2763 void DirtyLine(int y)
2767 if((y - this.viewY)*this.space.h < this.startY)
2768 this.startY = (y - this.viewY)*this.space.h + YOFFSET;
2769 if((y - this.viewY+1)*this.space.h > this.endY)
2770 this.endY = (y - this.viewY+1)*this.space.h-1 + YOFFSET;
2772 //ErrorLog("DirtyLine %d\n", y);
2779 if(style.recomputeSyntax)
2781 FigureStartSyntaxStates(lines.first, true);
2782 style.recomputeSyntax = false;
2785 if(this.startY > this.endY) return;
2786 if(this.startY <= 0 && this.endY >= clientSize.h-1)
2792 box.right = clientSize.w-1;
2793 box.top = this.startY;
2794 box.bottom = this.endY;
2797 this.startY = clientSize.h;
2801 bool IsMouseOnSelection()
2803 bool mouseOnSelection = false;
2806 int minY = Min(this.selY, this.y);
2807 int maxY = Max(this.selY, this.y);
2808 int minX = Min(this.selX, this.x);
2809 int maxX = Max(this.selX, this.x);
2811 FindMouse(this.mouseX - this.space.w / 2, this.mouseY, &x, &y, null, false);
2813 if(maxX != minX || maxY != minY)
2815 if(y > minY && y < maxY)
2816 mouseOnSelection = true;
2817 else if(y == minY && y == maxY)
2818 mouseOnSelection = (x < maxX && x >= minX);
2822 mouseOnSelection = (x >= this.selX);
2823 else if(y == this.y)
2824 mouseOnSelection = (x >= this.x);
2829 mouseOnSelection = (x < this.selX);
2830 else if(y == this.y)
2831 mouseOnSelection = (x < this.x);
2834 return mouseOnSelection;
2837 void UpdateCaretPosition(bool setCaret)
2841 if(mouseMove || (!overwrite && !style.noCaret))
2843 int max = this.mouseMove ? this.dropX : this.x;
2844 int y = this.mouseMove ? this.dropY : this.y;
2845 EditLine line = this.mouseMove ? this.dropLine : this.line;
2847 if(!(style.freeCaret))
2848 max = Min(max, line.count);
2850 if(FontExtent && display)
2852 for(c = 0; c < max; )
2861 for(len = 0; c < Min(max, line.count); c += numBytes)
2863 ch = line.buffer[c];
2864 numBytes = UTF8_NUM_BYTES(ch);
2866 if(ch == ' ' || ch == '\t')
2873 if(!len && ch == ' ')
2878 else if(!len && ch == '\t')
2880 w = (tabSize * space.w) - (x % (tabSize * space.w));
2884 FontExtent(display, this.font, line.buffer + start, len, &w, null);
2896 caretY = y * this.space.h;
2897 SetCaret(x + XOFFSET-2, y * space.h + YOFFSET, space.h);
2902 // TOFIX: Mismatch between NotifyCaretMove() and NotifyDropped() / GoToPosition()
2903 NotifyCaretMove(master, this, y + 1, x + 1);
2909 void SelectionEnables()
2911 if((x != selX || y != selY) && !selection)
2915 itemEditCut.disabled = false;
2916 itemEditDelete.disabled = false;
2918 itemEditCopy.disabled = false;
2920 this.selection = true;
2922 else if((x == selX && y == selY) && selection)
2924 itemEditCut.disabled = true;
2925 itemEditCopy.disabled = true;
2926 itemEditDelete.disabled = true;
2928 this.selection = false;
2932 void SetSelectCursor()
2934 if(!inactive || !style.noSelect)
2937 cursor = guiApp.GetCursor(arrow);
2938 else if(this.mouseSelect)
2939 cursor = guiApp.GetCursor(iBeam);
2942 if(IsMouseOnSelection())
2943 cursor = guiApp.GetCursor(arrow);
2945 cursor = guiApp.GetCursor(iBeam);
2950 int AdjustXPosition(EditLine line, int position, bool half, int * px, int max, int sc)
2953 int x = px ? *px : 0;
2960 if(c < Min(max, line.count))
2964 for(len = 0; c < Min(max, line.count); c += numBytes)
2966 ch = line.buffer[c];
2967 numBytes = UTF8_NUM_BYTES(ch);
2969 if(ch == ' ' || ch == '\t')
2976 if(!len && ch == ' ')
2981 else if(!len && ch == '\t')
2983 w = (tabSize * space.w) - (x % (tabSize * space.w));
2987 FontExtent(display, font, line.buffer + start, len, &w, null);
2991 if(style.freeCaret && c < max)
3000 if(x + (((half && len == 1) ? (w / 2) : w)) >= position)
3005 int a = start + len;
3008 while(a > 0 && !UTF8_IS_FIRST(line.buffer[--a]));
3012 FontExtent(display, font, line.buffer + start, a - start, &w, null);
3015 if(position > x + (half ? ((w + lastW) / 2) : lastW)) break;
3019 return Min(this.maxLineSize - 1, start + len);
3025 void SetCursorToViewX()
3027 bool selecting = this.x != selX || y != selY;
3029 // Horizontal Adjustment
3031 int c = AdjustXPosition(line, viewX, false, &x, MAXINT, 0);
3036 c = AdjustXPosition(line, viewX + clientSize.w - 1, false, &x, MAXINT, c);
3048 UpdateCaretPosition(false);
3054 void SetCursorToViewY()
3057 EditLine oldLine = this.line;
3059 bool selecting = this.x != this.selX || this.y != this.selY;
3061 numLines = clientSize.h / this.space.h;
3063 // Vertical Adjustment
3064 if(this.viewY > this.y)
3066 this.y = this.viewY;
3067 this.line = this.viewLine;
3070 if(this.viewY + numLines <= this.y)
3074 this.y = this.viewY-1;
3075 for(c = 0, line = this.viewLine; line && c<numLines; line = line.next, c++)
3082 if(this.line != oldLine)
3084 this.x = AdjustXPosition(this.line, caretX, true, null, MAXINT, 0);
3092 this.selLine = this.line;
3095 UpdateCaretPosition(false);
3102 bool SaveFile(char * fileName)
3104 File f = eFile_Open(fileName, FO_WRITE);
3108 eWindow_SetModified(false);
3109 eInstance_Delete(f);
3116 bool OnActivate(bool active, Window previous, bool * goOnWithActivation, bool direct)
3120 if(!style.multiLine)
3122 x = (active && this.active && !style.readOnly) ? line.count : 0;
3125 SetViewToCursor(true);
3130 if(!active && modified)
3133 if(!NotifyModified(master, this))
3136 *goOnWithActivation = false;
3143 if(timer) timer.Stop();
3145 mouseSelect = false;
3153 int aw = maxLength + 12, ah = Max(lineCount, 1) * space.h + 2;
3154 int nw = minClientSize.w, nh = minClientSize.h, xw = maxClientSize.w, xh = maxClientSize.h;
3155 clientSize = { nw && aw < nw ? nw : xw && aw > xw ? xw : aw, nh && ah < nh ? nh : xh && ah > xh ? xh : ah };
3158 bool OnResizing(int *w, int *h)
3164 *w = space.h * 80 / 14;
3168 void OnResize(int w, int h)
3171 if(!hasHorzScroll && !hasVertScroll && viewLine)
3172 SetViewToCursor(true);
3174 //if(!hasHorzScroll && !hasVertScroll && viewLine)
3176 SetViewToCursor(true);
3181 bool OnMiddleButtonDown(int x, int y, Modifiers mods)
3183 if(style.readOnly) return true;
3184 // We really shouldn't be pasting here, Unix middle button paste is for the selection (not the clipboard), good for the terminal
3185 // Middle button already serves as a dragger as well.
3190 bool OnRightButtonDown(int x, int y, Modifiers mods)
3192 this.rightButtonDown = true;
3197 bool OnRightButtonUp(int x, int y, Modifiers mods)
3200 if(!parent.inactive && rightButtonDown)
3203 Menu contextMenu { };
3205 MenuItem { contextMenu, $"Cut\tCtrl+X", t, NotifySelect = itemEditCut.NotifySelect, disabled = !selection || style.readOnly };
3206 MenuItem { contextMenu, $"Copy\tCtrl+C", c, NotifySelect = itemEditCopy.NotifySelect, disabled = !selection };
3207 MenuItem { contextMenu, $"Paste\tCtrl+V", p, NotifySelect = itemEditPaste.NotifySelect, disabled = style.readOnly };
3208 MenuItem { contextMenu, $"Delete\tDel", d, NotifySelect = itemEditDelete.NotifySelect, disabled = !selection || style.readOnly };
3209 MenuDivider { contextMenu };
3210 MenuItem { contextMenu, $"Select All\tCtrl+A", a, NotifySelect = itemEditSelectAll.NotifySelect };
3212 popup = PopupMenu { master = this, menu = contextMenu,
3214 nonClient = true, interim = false, parent = parent,
3215 position = { x + clientStart.x + parent.clientStart.x + position.x, y + cientStart.y + parent.sy + clientStart.y + position.y };
3217 position = { x + clientStart.x + absPosition.x - guiApp.desktop.position.x, y + clientStart.y + absPosition.y - guiApp.desktop.position.y }
3221 rightButtonDown = false;
3225 bool OnLeftButtonDown(int mx, int my, Modifiers mods)
3230 if(style.noSelect) return true;
3232 // Should we have a separate 'selectOnActivate' style?
3233 if(!mods.isActivate || (style.readOnly && style.multiLine))
3239 mouseX = mx - XOFFSET;
3242 FindMouse(mouseX, mouseY, &x, &y, &line, true);
3244 //PrintLn("OnLeftButtonDown: ", x, ", ", y);
3250 else if(IsMouseOnSelection() && !mods.isActivate)
3260 if(!mouseMove && !wordSelect && (!mods.isActivate || style.multiLine))
3262 if(mods.shift && !mods.isActivate)
3283 UpdateCaretPosition(true);
3284 // Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
3285 // ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
3289 bool OnLeftButtonUp(int x, int y, Modifiers mods)
3293 mouseSelect = false;
3304 FindMouse(mouseX, mouseY, &x, &y, &line, true);
3306 //PrintLn("MouseMove: ", x, ", ", y);
3312 mouseMove = IsMouseOnSelection();
3316 int size = SelSize();
3319 char * text = new char[size+1];
3323 GetSel(text, false);
3325 if(Max(selY, this.y) == dropY)
3329 if(this.dropX > this.selX)
3330 moveX = this.x - this.selX;
3334 if(this.dropX > this.x)
3335 moveX = this.selX - this.x;
3339 recordUndoEvent = true;
3341 this.dropX -= moveX;
3342 this.selX = this.x = this.dropX;
3343 this.selY = this.y = this.dropY;
3344 this.selLine = this.line = this.dropLine;
3346 recordUndoEvent = false;
3348 SetViewToCursor(true);
3353 byte * c = ((EditBox)this).multiLineContents, * c2;
3354 int l1 = c ? strlen(c) : 0, l2;
3357 c2 = ((EditBox)this).multiLineContents;
3358 l2 = c2 ? strlen(c2) : 0;
3359 if(l1 != l2 || (l1 && strcmp(c, c2)))
3389 FindMouse(mouseX, mouseY, &x, &y, &line, true);
3391 //PrintLn("Dropped: ", x, ", ", y);
3393 NotifyDropped(master, this, x, y);
3397 // Return false because DataBoxes automatically set EditBox editor's clickThrough to true for MouseMove events
3398 // ( for tool tips -- see 95ee4962c4c7bc3fe0a04aa6a4f98cacada40884)
3402 bool OnMouseMove(int mx, int my, Modifiers mods)
3408 if(mods != -1 && mods.isSideEffect)
3413 if(style.noSelect) return true;
3414 if(wordSelect) return true;
3415 mouseX = mx - XOFFSET;
3418 needScroll = FindMouse(this.mouseX, this.mouseY, &x, &y, &line, true);
3420 if(this.mouseMove || this.mouseSelect)
3429 ((style.hScroll) || (style.vScroll)))
3436 DirtyLine(this.dropY);
3439 DirtyLine(this.dropY);
3440 this.dropLine = line;
3441 SetViewToCursor(true);
3443 //PrintLn("MouseMove: ", "dropX = ", x, ", dropY = ", y);
3446 else if(this.mouseSelect)
3448 DirtyLine(this.selY);
3455 SetViewToCursor(true);
3458 //PrintLn("MouseSelect: ", "x = ", x, ", y = ", y);
3465 bool OnLeftDoubleClick(int mx, int my, Modifiers mods)
3470 //PrintLn("OnLeftDoubleClick: ", mx, ", ", my, ", mods = ", mods);
3474 if(style.noSelect) return true;
3475 FindMouse(mx, my, &x, &y, &line, false);
3476 if(!NotifyDoubleClick(master, this, line, mods))
3483 for(c = x; c >= 0; c--)
3486 while(c > 0 && !UTF8_IS_FIRST(line.buffer[c])) c--;
3487 ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3494 for(c = start; c<line.count; c += numBytes)
3496 unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3497 if(!ch || !IS_ALUNDER(ch))
3507 this.line = this.selLine = line;
3508 this.wordSelect = (c != start);
3519 Menu fileMenu { menu, "File", F };
3520 saveDialog = fileDialog;
3521 MenuItem { fileMenu, $"Save\tCtrl+S", S, CtrlS, NotifySelect = MenuFileSave };
3522 MenuItem { fileMenu, $"Save As...", A, NotifySelect = MenuFileSaveAs };
3524 if(style.autoSize) AutoSize();
3532 FontExtent(display, font, " ", 1, (int *)&space.w, (int *)&space.h);
3533 FontExtent(display, font, "W", 1, (int *)&large.w, (int *)&large.h);
3535 space.w = Max(space.w, 1);
3536 large.w = Max(large.w, 1);
3537 space.h = Max(space.h, 1);
3538 large.h = Max(large.h, 1);
3542 for(line = lines.first; line; line = line.next)
3543 ComputeLength(line);
3548 SetViewToCursor(true);
3556 bool OnLoadGraphics()
3558 FontExtent = Display::FontExtent;
3561 // UpdateCaretPosition(true);
3565 void OnUnloadGraphics()
3570 bool OnKeyHit(Key key, unichar ch)
3572 bool shift = (key.shift) ? true : false;
3574 //PrintLn("OnKeyHit: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
3576 if(!ch && !key.alt && !key.ctrl)
3578 key.code = (SmartKey)key.code;
3580 else if(!ch && key.alt)
3583 switch(key.code) //(ch || key.alt || key.ctrl) ? key.code : (Key)(SmartKey)key.code)
3586 if(style.readOnly) break;
3587 if(style.stuckCaret) GoToEnd(true);
3588 if(!(style.freeCaret))
3590 this.x = Min(this.x, this.line.count);
3596 EditLine line = this.line;
3598 for(y = this.y; y>= 0; y--)
3600 c = (y == this.y) ? (Min(this.x-1, line.count-1)) : line.count-1;
3602 // Slow down when going on lines...
3603 if(y != this.y) break;
3607 //if(this.line.buffer[c] != '\t' && this.line.buffer[c] != ' ')
3608 if(IS_ALUNDER(line.buffer[c]))
3613 if(!IS_ALUNDER(line.buffer[c]))
3615 //if(this.line.buffer[c] == '\t' || this.line.buffer[c] == ' ')
3630 _DelCh(line, y, c+1, this.line, this.y, this.x, true, false, null);
3631 this.x = this.selX = Min(c+1, line.count);
3632 this.y = this.selY = y;
3633 this.line = this.selLine = line;
3634 SetViewToCursor(true);
3645 if(style.readOnly) break;
3646 if(style.stuckCaret) break;
3655 if(this.line != this.selLine || this.x != this.selX)
3658 SetViewToCursor(true);
3663 EditLine line1 = this.line, line2 = this.line;
3664 int x1, y1 = this.y, x2, y2 = this.y;
3665 if(!style.freeCaret)
3667 this.selX = this.x = Min(this.x, this.line.count);
3672 if(x1 < line1.count)
3679 char * buffer = line1.buffer;
3680 for(i = x1; i < line1.count; i++)
3682 if(!IS_ALUNDER(buffer[i]))
3686 for(; i < line1.count; i++)
3688 // Delete trailing whitespace
3689 if(IS_ALUNDER(buffer[i]))
3699 // Avoid creating trailing spaces if there is no content on next line
3702 if(line2 && !line2.count)
3707 _DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
3708 SetViewToCursor(true);
3717 if(!key.alt && !key.ctrl)
3721 bool stuffAfter = false;
3724 /*bool resetX = false;
3727 if(style.stuckCaret) GoToEnd(true);
3728 if(style.readOnly) break;
3729 if(!(style.multiLine)) break;
3731 for(c = 0; c<this.line.count && c<this.x; c++)
3733 if(this.line.buffer[c] == '\t')
3734 position += this.tabSize - (position % this.tabSize);
3735 else if(this.line.buffer[c] == ' ')
3741 // Prevent adding trailing spaces if at the head of a line
3742 /*if(c && c == this.x && c < this.line.count && this.x == this.selX && this.y == this.selY)
3754 if(this.x < this.line.count)
3757 //If last character is a { indent one tab
3758 if(this.line.buffer[this.x - 1] == '{')
3760 //Except if the next non space character is a }
3761 bool indent = false;
3763 for(i = this.x; i < this.line.size; i++)
3764 if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
3766 if(this.line.buffer[i] != '}')
3771 position += this.tabSize;
3774 addString = new char[position + 2];
3775 addString[len++] = '\n';
3776 addString[len] = '\0';
3778 if(stuffAfter || !style.freeCaret)
3780 for(c = 0; c<position; )
3782 if(style.useTab && c + this.tabSize <= position)
3784 addString[len++] = '\t';
3789 addString[len++] = ' ';
3793 addString[len] = '\0';
3795 recordUndoEvent = true;
3798 EditLine prevLine = this.line.prev;
3801 // Nuke spaces if that is all that is left on previous line
3803 char * buffer = prevLine.buffer;
3804 for(i = 0; i < prevLine.count; i++)
3805 if(buffer[i] != ' ' && buffer[i] != '\t')
3807 if(i == prevLine.count)
3808 DelCh(prevLine, this.y - 1, 0, prevLine, this.y - 1, prevLine.count, false);
3812 this.x = this.selX = backX;
3815 else */if(!stuffAfter && style.freeCaret)
3817 this.x = this.selX = position;
3821 SetViewToCursor(true);
3824 recordUndoEvent = false;
3832 if(style.stuckCaret) break;
3833 if(!(style.freeCaret))
3835 this.x = Min(this.x, this.line.count);
3836 this.selX = Min(this.selX, this.selLine.count);
3839 if(!shift) SelDirty();
3842 bool foundAlpha = false;
3845 EditLine line, lastLine;
3848 for(line = this.line; (line && !found); line = line.prev, y--)
3853 if(this.x == 0 && line != this.line)
3862 if(line == this.line) start = this.x -1; else start = line.count-1;
3863 start = Min(start, line.count-1);
3865 for(c = start; c >= 0;)
3868 unichar ch = UTF8_GET_CHAR(line.buffer + c, numBytes);
3886 byte ch = line.buffer[c];
3887 if(UTF8_IS_FIRST(ch)) break;
3891 // No next word found,
3892 if(!found && ( this.x > 0 || (!line.count && this.x)))
3906 this.line = lastLine;
3917 byte * buffer = line.buffer;
3920 byte ch = buffer[x];
3921 if(UTF8_IS_FIRST(ch)) break;
3938 if(!shift) _Deselect();
3939 SetViewToCursor(true);
3945 if(style.stuckCaret) break;
3946 if(!(style.freeCaret))
3948 this.x = Min(this.x, this.line.count);
3949 this.selX = Min(this.selX, this.selLine.count);
3952 if(!shift) SelDirty();
3953 if(!shift && (this.x != this.selX || this.y != this.selY));
3956 bool onAChar = false;
3957 if(this.selX != this.x || this.selY != this.y)
3959 if(this.x<this.line.count)
3960 if(this.line.buffer[this.x] != '\t' && this.line.buffer[this.x] !=' ')
3962 if(key.shift && onAChar &&
3963 ((this.y > this.selY)||((this.selY == this.y)&&(this.x >= this.selX))))
3965 bool foundAlpha = false;
3967 EditLine line, lastLine;
3969 int lastC, lastY, lastNumBytes;
3971 for(line = this.line; (line && !found); line = line.next, y++)
3973 int start = (line == this.line) ? this.x : 0;
3977 for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
3983 lastNumBytes = numBytes;
3993 if(!found && (c != this.x || line != this.line))
4006 this.x = lastC + lastNumBytes;
4008 this.line = lastLine;
4015 bool foundAlpha = false;
4020 for(line = this.line; (line && !found); line = line.next, y++)
4022 int start = (line == this.line) ? this.x : 0;
4026 for(c = start; c < line.count && (ch = UTF8_GET_CHAR(line.buffer + c, numBytes)); c += numBytes)
4042 // No next word found,
4043 if(!found && (c != this.x || line != this.line))
4047 this.x = line.count;
4059 if(x < line.count || (style.freeCaret && line.count < maxLineSize))
4063 byte * buffer = line.buffer;
4066 byte ch = buffer[x];
4067 if(UTF8_IS_FIRST(ch)) break;
4088 if(!shift) _Deselect();
4089 SetViewToCursor(true);
4096 if(!style.vScroll || hasVertScroll) break;
4102 if(style.stuckCaret) break;
4104 if(!shift) SelDirty();
4114 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4118 if(!shift) _Deselect();
4120 SetViewToCursor(false);
4123 if(caretY == this.y * space.h)
4129 if(!style.freeCaret)
4130 this.x = Min(this.x, line.count);
4140 int sx = 0, sy = this.y * space.h;
4141 char * text = line.text;
4142 int maxW = clientSize.w - sx;
4143 display.FontExtent(font, " ", 1, null, &th);
4147 int startPos = textPos;
4150 bool lineComplete = false;
4152 if(!style.wrap && caretY == MAXINT)
4155 //textPos = line.count;
4156 //lineComplete = true;
4159 for(; (style.freeCaret || textPos < line.count) && !lineComplete;)
4163 char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4166 len = (nextSpace - (text + textPos));
4168 len = line.count - textPos;
4170 if(textPos < line.count)
4172 display.FontExtent(font, text + textPos, len, &w, null);
4174 if(nextSpace) { w += space.w; len++; }
4176 if(style.wrap && x + width + w > maxW && x > 0)
4178 lineComplete = true;
4188 if((!style.freeCaret && textPos >= line.count) || (sy == caretY - th && caretX <= x + width + sx))
4192 while(this.x > 0 && x + sx > caretX && this.x > startPos)
4195 if(this.x > line.count)
4198 while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4199 len = this.x - startPos;
4200 display.FontExtent(font, text + startPos, len, &x, null);
4204 if(!shift) _Deselect();
4206 SetViewToCursor(false);
4210 if(sy == caretY - th || textPos >= line.count)
4212 if(textPos >= line.count)
4214 int c = textPos - 1;
4215 while(c > 0 && text[c] == ' ') c--;
4219 this.x = line.count;
4222 if(!shift) _Deselect();
4224 SetViewToCursor(false);
4229 } while(textPos < line.count);
4232 if(!shift) _Deselect();
4234 SetViewToCursor(false);
4243 int x = AdjustXPosition(this.line, this.line.prev, true, null, MAXINT, 0);
4244 if(!shift) SelDirty();
4245 this.line = this.line.prev;
4251 if(!shift) _Deselect();
4255 SetViewToCursor(false);
4261 return style.multiLine ? false : true;
4265 if(!style.vScroll || hasVertScroll)
4272 if(style.stuckCaret) break;
4276 int sx = 0, sy = this.y * this.space.h;
4277 int maxW = clientSize.w - sx;
4278 char * text = line.buffer;
4280 if(!shift) SelDirty();
4286 if(AdjustXPosition(line, maxW, this.x, line.count, true, null, MAXINT, 0) <= line.count)
4296 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
4300 if(!shift) _Deselect();
4302 SetViewToCursor(false);
4305 while(!textPos || (style.freeCaret || textPos<line.count))
4307 int startPos = textPos;
4310 bool lineComplete = false;
4311 if(!style.wrap && sy <= caretY)
4313 textPos = line.count;
4314 lineComplete = true;
4316 for(; (style.freeCaret || textPos<line.count) && !lineComplete;)
4320 char * nextSpace = (textPos < line.count) ? strchr(text + textPos, ' ') : (text + textPos);
4323 len = (nextSpace - (text + textPos));
4325 len = line.count - textPos;
4327 if(textPos < line.count)
4329 display.FontExtent(font, text + textPos, len, &w, &th);
4331 if(nextSpace) { w += space.w; len++; }
4332 if(style.wrap && x + width + w > maxW && x > 0)
4334 lineComplete = true;
4344 if(sy > caretY && ((!style.freeCaret && textPos >= line.count) || caretX <= x + width + sx))
4348 while(this.x > 0 && x + sx > caretX && textPos > startPos)
4351 if(this.x > line.count)
4354 while(this.x > 0 && !UTF8_IS_FIRST(text[--this.x]));
4356 len = this.x - startPos;
4357 display.FontExtent(font, text + startPos, len, &x, null);
4360 if(!shift) _Deselect();
4363 SetViewToCursor(false);
4369 this.x = line.count;
4372 if(!shift) _Deselect();
4375 SetViewToCursor(false);
4378 else if(textPos >= line.count && line.next)
4384 sy = this.y * this.space.h;
4385 sx = 0; //textBlock.startX;
4391 sx = 0; //textBlock.startX;
4399 this.x = Min(this.x, line.count);
4400 //int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4402 if(!shift) _Deselect();
4405 if(this.selX != this.x || this.selY != this.y)
4408 SetViewToCursor(false);
4415 int x = AdjustXPosition(this.line, this.line.next, true, null, MAXINT, 0);
4416 if(!shift) SelDirty();
4417 this.line = this.line.next;
4421 if(!shift) _Deselect();
4424 if(this.selX != this.x || this.selY != this.y)
4432 return style.multiLine ? false : true;
4435 if(style.stuckCaret) break;
4436 if(!style.multiLine && key.ctrl) break;
4437 if(!(style.freeCaret))
4438 this.selX = Min(this.selX, this.selLine.count);
4440 if(!shift) SelDirty();
4443 this.line = this.lines.first;
4444 if(this.y != 0 || this.x != 0)
4454 EditLine line = this.line;
4456 for(c=0; line.buffer[c]; c++)
4457 if(line.buffer[c] != ' ' && line.buffer[c] != '\t')
4459 if(shift && (c != 0 || this.x))
4468 if(shift && this.x != 0)
4474 if(!shift) _Deselect();
4475 SetViewToCursor(true);
4481 if(style.stuckCaret) break;
4482 if(!style.multiLine && key.ctrl) break;
4483 if(!style.freeCaret)
4484 this.selX = Min(this.selX, this.selLine.count);
4486 if(!shift) SelDirty();
4491 else if(this.x != this.line.count)
4493 this.x = this.line.count;
4498 if(!shift) _Deselect();
4499 SetViewToCursor(true);
4504 if(style.tabKey && !key.ctrl)
4506 if(this.selY != this.y && style.tabSel)
4508 EditLine firstLine, lastLine;
4512 // Do multi line selection tabbing here
4513 if(this.selY < this.y)
4515 firstLine = this.selLine;
4516 lastLine = this.line;
4522 // Selecting going up
4523 firstLine = this.line;
4524 lastLine = this.selLine;
4531 for(line = firstLine; line; line = line.next, y++)
4533 if(line != lastLine || x)
4537 BufferLocation before = { line, y, 0 }, after = { line, y, 0 };
4539 for(c=0; c<line.count && lastC < this.tabSize; c++, lastC++)
4541 if(line.buffer[c] == '\t')
4546 else if(line.buffer[c] != ' ')
4551 NotifyCharsDeleted(master, this, &before, &after, this.pasteOperation);
4554 int len = GetText(null, line, y, 0, line, y, lastC, false, false);
4555 char * string = new char[len];
4556 DelTextAction action { y1 = y, x1 = 0, y2 = y, x2 = lastC, string = string, placeAfter = true };
4557 GetText(string, line, y, 0, line, y, lastC, false, false);
4560 memmove(line.buffer,line.buffer+lastC,line.size-lastC);
4561 if(!line.AdjustBuffer(line.count-lastC))
4567 if(line == lastLine) break;
4572 for(line = firstLine; line; line = line.next, y++)
4576 if(line != lastLine || x)
4580 if(line.count + 1 <= this.maxLineSize)
4582 BufferLocation before = { line, y, 0 }, after = { line, y, 1 };
4584 if(!line.AdjustBuffer(line.count+1))
4587 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4589 AddCharAction action { ch = '\t', x = 0, y = y };
4593 memmove(line.buffer+1,line.buffer,line.size-1);
4595 line.buffer[0] = '\t';
4600 if(line.count + this.tabSize <= this.maxLineSize)
4603 BufferLocation before = { line, y, 0 }, after = { line, y, this.tabSize };
4604 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
4606 if(!line.AdjustBuffer(line.count+this.tabSize))
4610 char * string = new char[this.tabSize + 1];
4611 memset(string, ' ', this.tabSize);
4612 string[this.tabSize] = '\0';
4613 Record(AddTextAction { string = string, x1 = 0, y1 = y, x2 = this.tabSize, y2 = y });
4616 memmove(line.buffer+this.tabSize,line.buffer,line.size-(this.tabSize));
4617 line.count+=this.tabSize;
4618 for(c=0; c<this.tabSize; c++)
4619 line.buffer[c] = ' ';
4625 if(line == lastLine) break;
4628 ComputeLength(maxLine);
4640 char * addString = new char[this.tabSize + 1];
4642 if(!(style.freeCaret))
4644 this.x = Min(this.x, this.line.count);
4648 start = Min(this.x, this.selX);
4649 for(c=start; ((c == start) || ((c) % this.tabSize)); c++)
4651 addString[len++] = ' ';
4659 SetViewToCursor(true);
4668 if(!(style.hScroll) || hasHorzScroll) break;
4669 if(this.viewX < this.maxLength)
4671 //this.viewX+=this.space.w*this.tabSize;
4673 SetScrollPosition((this.viewX + this.space.w*this.tabSize), this.viewY * this.space.h);
4680 if(!shift) _Deselect();
4692 if(!(style.hScroll) || hasHorzScroll) break;
4695 //this.viewX-=this.space.w*this.tabSize;
4696 //this.viewX = Max(this.viewX,0);
4698 SetScrollPosition((this.viewX-this.space.w*this.tabSize), this.viewY * this.space.h);
4699 // SetCursorToView();
4706 if(!shift) _Deselect();
4719 else if(!style.readOnly)
4723 if(!(style.readOnly))
4729 this.overwrite ^= 1;
4730 UpdateCaretPosition(true);
4735 NotifyOvrToggle(master, this, this.overwrite);
4747 if(style.noSelect) break;
4750 //Save current view position
4751 int tempX = this.viewX;
4752 int tempY = this.viewY;
4756 this.selLine = this.lines.first;
4757 this.y = this.lineCount-1;
4758 this.line = this.lines.last;
4759 this.x = this.line.count;
4762 SetViewToCursor(true);
4764 //Restore previous view position
4765 SetScrollPosition(tempX, tempY * this.space.h);
4771 // TOCHECK: Was there any good reason why we weren't returning false here?
4772 return false; // break;
4777 if(!(style.readOnly))
4785 if(style.readOnly) break;
4791 if(style.readOnly) break;
4796 if(style.readOnly) break;
4800 if(style.readOnly) break;
4804 if(style.readOnly) break;
4805 if(key.shift && key.code == rightBracket)
4807 //Only indent back if you are exactly at one tab.
4809 bool whitespace = true;
4815 EditLine line = this.line;
4817 //Only remove one tab if there is nothing else on the line.
4818 for(i = 0; i < this.line.count; i++)
4820 if(this.line.buffer[i] != ' ' && this.line.buffer[i] != '\t')
4834 for(pos = line.count - 1; pos >= 0; pos--)
4836 char c = line.buffer[pos];
4840 indentwidth += this.tabSize;
4842 //Counting backwards, so when you find a character, indentation is reset
4852 //Place the } to get an undo:
4855 this.x = this.line.count;
4859 putsize = indentwidth;
4861 putsize = indentwidth / this.tabSize + indentwidth % this.tabSize;
4863 newline = new char[putsize+2];
4864 newline[putsize] = '}';
4865 newline[putsize+1] = '\0';
4869 for(; i < indentwidth / this.tabSize; i++)
4871 for(;i < putsize; i++)
4880 else if(!key.ctrl && !key.alt && ch != 128 && ch >= 32)
4892 void OnHScroll(ScrollBarAction action, int position, Key key)
4895 //PrintLn("OnHScroll: ", action, ", pos = ", position, ", key = ", key);
4897 this.viewX = position;
4898 if(action != setRange)
4900 if(!this.mouseMove && style.cursorFollowsView)
4907 void OnVScroll(ScrollBarAction action, int position, Key key)
4909 int oldViewY = this.viewY;
4912 //PrintLn("OnVScroll: ", action, ", pos = ", position, ", key = ", key);
4914 position /= this.space.h;
4916 if(position < this.viewY)
4918 for(; position < this.viewY && this.viewLine.prev; this.viewLine = this.viewLine.prev, this.viewY--);
4919 style.recomputeSyntax = true;
4921 else if(position > this.viewY)
4923 EditLine oldViewLine = viewLine;
4924 for(; position > this.viewY && this.viewLine.next; this.viewLine = this.viewLine.next, this.viewY++);
4925 FigureStartSyntaxStates(oldViewLine, false);
4928 if(action != setRange)
4930 if(!this.mouseMove && style.cursorFollowsView && !SelSize()) SetCursorToViewY();
4933 if(this.x != this.selX || this.y != this.selY)
4937 Scroll(0, (this.viewY - oldViewY) * this.space.h);
4940 int numLines = clientSize.h / this.space.h;
4942 if(Abs(this.viewY - oldViewY) < numLines)
4945 if(this.viewY > oldViewY)
4947 for(y = oldViewY; y <this.viewY; y++)
4948 DirtyLine(y + numLines);
4952 for(y = this.viewY; y <oldViewY; y++)
4953 DirtyLine(y + numLines);
4958 // Fix dirt of stuff before first line...
4959 if(this.viewY - oldViewY > 0)
4961 Box box { 0,0, clientSize.w-1, YOFFSET-1 };
4968 bool _AddCh(unichar ch, int * addedSpacesPtr, int * addedTabsPtr)
4973 ReplaceTextAction replaceAction = null;
4974 AddCharAction addCharAction = null;
4975 int addedSpaces = 0, addedTabs = 0;
4977 if(ch == '\r') return true;
4978 if(style.stuckCaret /*|EES_READONLY)*/ )
4981 if(ch == '\n' && !(style.multiLine) && this.line) return false;
4983 if(!undoBuffer.dontRecord)
4985 if(selX != x || selY != y)
4990 int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
4991 oldString = new char[len];
4992 UTF32toUTF8Len(&ch, 1, buffer, 4);
4993 newString = CopyString(buffer);
4994 GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
4996 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
4997 if(selY < y || (selY == y && selX < x))
4999 replaceAction.x1 = selX;
5000 replaceAction.y1 = selY;
5001 replaceAction.x2 = x;
5002 replaceAction.y2 = y;
5006 replaceAction.x1 = x;
5007 replaceAction.y1 = y;
5008 replaceAction.x2 = selX;
5009 replaceAction.y2 = selY;
5011 Record(replaceAction);
5012 undoBuffer.dontRecord++;
5016 addCharAction = AddCharAction { y = y, x = x, ch = ch };
5017 Record(addCharAction);
5023 DelSel(&addedSpaces);
5024 if(this.lineCount+1 > this.maxLines)
5027 Emptyline(this.lines.first,0);
5031 if(!(style.autoSize && (!maxClientSize.h || maxClientSize.h > clientSize.h + this.space.h)) && !(style.vScroll))
5033 // Make sure it fits, but we need a default line is this.font is too big for window
5034 if(this.space.h * (this.lineCount+1) > clientSize.h && this.line)
5037 if((this.y >= 0) && this.y < this.viewY)
5042 line = EditLine { };
5045 lines.Insert(this.line, line);
5046 line.editBox = this;
5052 // If we're displacing the lines from a current line ...
5055 if(this.line.buffer)
5058 if(this.line.count < endX) endX = this.line.count;
5059 length = this.line.count - endX;
5062 if(!line.AdjustBuffer(length))
5066 if(this.line.buffer)
5068 CopyBytes(line.buffer,this.line.buffer+endX, length+1);
5070 if(endX > 4000 || endX < 0)
5073 this.line.count = endX;
5074 this.line.buffer[this.line.count] = '\0';
5075 this.line.AdjustBuffer(this.line.count);
5076 ComputeLength(this.line);
5080 BufferLocation before = { this.line, this.y, this.x }, after;
5085 line.count = length;
5088 if(length > 4000 || length < 0)
5091 ComputeLength(this.line);
5096 line.buffer[line.count] = '\0';
5099 after.line = this.line, after.y = this.y, after.x = this.x;
5101 NotifyCharsAdded(master, this, &before, &after, this.pasteOperation);
5107 int count = UTF32toUTF8Len(&ch, 1, string, 5);
5108 DelSel(&addedSpaces);
5109 result = AddToLine(string, count, false, addedSpaces ? null : &addedSpaces, &addedTabs);
5110 if(addedSpacesPtr) *addedSpacesPtr = addedSpaces;
5111 if(addedTabsPtr) *addedTabsPtr = addedTabs;
5115 this.selLine = this.line;
5119 replaceAction.x3 = x;
5120 replaceAction.y3 = y;
5121 replaceAction.addedSpaces = addedSpaces;
5122 replaceAction.addedTabs = addedTabs;
5123 undoBuffer.dontRecord--;
5127 addCharAction.x -= addedTabs * (tabSize-1);
5128 addCharAction.addedSpaces = addedSpaces;
5129 addCharAction.addedTabs = addedTabs;
5136 /****************************************************************************
5138 ****************************************************************************/
5141 bool AddCh(unichar ch)
5143 return _AddCh(ch, null, null);
5148 this.modified = true;
5149 NotifyUpdate(master, this);
5150 modifiedDocument = true;
5153 void Delete(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5156 _DelCh(line1, y1, x1, line2, y2, x2, false, false, null);
5157 SetViewToCursor(true);
5165 itemEditUndo.disabled = undoBuffer.curAction == 0;
5166 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5167 if(savedAction == undoBuffer.curAction)
5169 modifiedDocument = false;
5171 NotifyUnsetModified(master, this);
5178 itemEditUndo.disabled = undoBuffer.curAction == 0;
5179 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5180 if(savedAction == undoBuffer.curAction)
5182 modifiedDocument = false;
5184 NotifyUnsetModified(master, this);
5188 void Record(UndoAction action)
5190 if(!undoBuffer.dontRecord)
5192 undoBuffer.Record(action);
5193 itemEditUndo.disabled = undoBuffer.curAction == 0;
5194 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
5201 this.lines.first, 0,0,
5202 this.lines.last, this.lines.count-1, strlen(((EditLine)this.lines.last).buffer));
5205 void Select(EditLine line1, int y1, int x1, EditLine line2, int y2, int x2)
5210 this.selLine = line1 ? (EditLine)line1 : this.lines.first;
5211 this.x = line2 ? x2 : ((EditLine)this.lines.last).count;
5212 this.y = line2 ? y2 : (this.lineCount-1);
5213 this.line = line2 ? (EditLine)line2 : this.lines.last;
5216 SetViewToCursor(true);
5220 // TODO: Fix this vs modifiedDocument window property
5221 void SetModified(bool flag)
5225 this.modified = false;
5226 if(flag && !NotifyModified(master, this))
5227 this.modified = true;
5232 bool AddS(char * string)
5239 int addedSpaces = 0, addedTabs = 0;
5240 AddTextAction action = null;
5241 ReplaceTextAction replaceAction = null;
5243 this.pasteOperation = true;
5245 if(style.stuckCaret /*|EES_READONLY)*/ )
5248 if(!undoBuffer.dontRecord)
5250 char * placeString = CopyString(string);
5251 if(!style.multiLine)
5255 for(i = 0; (ch = placeString[i]); i++)
5258 placeString[i] = '\0';
5259 placeString = renew placeString byte[i+1];
5264 if(selX != x || selY != y)
5268 int len = GetText(null, selLine, selY, selX, this.line, y, x, false, false);
5269 oldString = new char[len];
5270 newString = placeString;
5271 GetText(oldString, selLine, selY, selX, this.line, y, x, false, false);
5273 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5274 if(selY < y || (selY == y && selX < x))
5276 replaceAction.x1 = selX;
5277 replaceAction.y1 = selY;
5278 replaceAction.x2 = x;
5279 replaceAction.y2 = y;
5283 replaceAction.x1 = x;
5284 replaceAction.y1 = y;
5285 replaceAction.x2 = selX;
5286 replaceAction.y2 = selY;
5288 Record(replaceAction);
5292 if(string[0] == '\n')
5293 action = AddTextAction { y1 = y, x1 = Min(this.line.count, x), string = placeString };
5295 action = AddTextAction { y1 = y, x1 = x, string = placeString };
5303 undoBuffer.dontRecord++;
5304 DelSel(&addedSpaces);
5308 for(c = 0; string[c]; c++)
5310 if(string[c] == '\n' || string[c] == '\r')
5312 if(!AddToLine(line,count, true, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5317 if(string[c] == '\n')
5326 // Reset for next line
5330 if(string[c] == '\r' && *line == '\n')
5342 // Why was this here?
5345 // Add the line here
5347 if(!AddToLine(line,count,false, addedSpaces ? null : &addedSpaces, addedTabs ? null : &addedTabs))
5353 undoBuffer.dontRecord--;
5358 action.addedSpaces = addedSpaces;
5359 action.addedTabs = addedTabs;
5361 else if(replaceAction)
5363 replaceAction.y3 = y;
5364 replaceAction.x3 = x;
5365 replaceAction.addedSpaces = addedSpaces;
5366 replaceAction.addedTabs = addedTabs;
5369 UpdateCaretPosition(true);
5371 this.pasteOperation = false;
5384 DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5385 SetViewToCursor(true);
5391 void PutCh(unichar ch)
5395 if((ch >= 32 /*&& ch <=126*/) || ch == '\n')
5396 //if((ch >= 32) || ch == '\n')
5398 int addedSpaces = 0, addedTabs = 0;
5399 ReplaceTextAction replaceAction = null;
5400 AddCharAction addCharAction = null;
5403 ch = (ch < 128) ? toupper(ch) : ch; // TODO: UNICODE TO UPPER
5405 if(this.overwrite && selX == x && selY == y && this.x < this.line.count)
5410 int len = GetText(null, line, y, x, line, y, x+1, false, false);
5411 oldString = new char[len];
5412 UTF32toUTF8Len(&ch, 1, buffer, 4);
5413 newString = CopyString(buffer);
5414 GetText(oldString, line, y, x, line, y, x+1, false, false);
5415 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = false };
5416 replaceAction.x1 = x;
5417 replaceAction.y1 = y;
5418 replaceAction.x2 = x+1;
5419 replaceAction.y2 = y;
5420 Record(replaceAction);
5422 undoBuffer.dontRecord++;
5423 DelCh(this.line, this.y, this.x, this.line, this.y, this.x+1, true);
5424 undoBuffer.dontRecord--;
5426 else if(!undoBuffer.dontRecord)
5428 if(selX != x || selY != y)
5433 int len = GetText(null, line, y, x, selLine, selY, selX, false, false);
5434 oldString = new char[len];
5435 UTF32toUTF8Len(&ch, 1, buffer, 4);
5436 newString = CopyString(buffer);
5437 GetText(oldString, line, y, x, selLine, selY, selX, false, false);
5438 replaceAction = ReplaceTextAction { newString = newString, oldString = oldString, placeAfter = true };
5439 if(selY < y || (selY == y && selX < x))
5441 replaceAction.x1 = selX;
5442 replaceAction.y1 = selY;
5443 replaceAction.x2 = x;
5444 replaceAction.y2 = y;
5448 replaceAction.x1 = x;
5449 replaceAction.y1 = y;
5450 replaceAction.x2 = selX;
5451 replaceAction.y2 = selY;
5453 Record(replaceAction);
5457 addCharAction = AddCharAction { y = y, x = x, ch = ch };
5458 Record(addCharAction);
5461 undoBuffer.dontRecord++;
5462 result = _AddCh(ch, &addedSpaces, &addedTabs);
5465 replaceAction.x3 = x;
5466 replaceAction.y3 = y;
5467 replaceAction.addedSpaces = addedSpaces;
5468 replaceAction.addedTabs = addedTabs;
5472 addCharAction.x -= addedTabs * (tabSize-1);
5473 addCharAction.addedSpaces = addedSpaces;
5474 addCharAction.addedTabs = addedTabs;
5476 undoBuffer.dontRecord--;
5480 if(result) SetViewToCursor(true);
5484 void PutS(char * string)
5489 SetViewToCursor(true);
5494 void Printf(char * format, ...)
5498 char temp[MAX_F_STRING];
5500 va_start(args, format);
5501 vsnprintf(temp, sizeof(temp), format, args);
5502 temp[sizeof(temp)-1] = 0;
5508 void SetContents(char * format, ...)
5512 undoBuffer.dontRecord++;
5514 DelCh(this.lines.first, 0, 0, this.lines.last, this.lineCount-1, ((EditLine)(this.lines.last)).count, true);
5517 char temp[MAX_F_STRING];
5519 va_start(args, format);
5520 vsnprintf(temp, sizeof(temp), format, args);
5521 temp[sizeof(temp)-1] = 0;
5528 undoBuffer.dontRecord--;
5541 x -= 1 + _DelCh(line, y, x-1, line, y, x, true, false, null);
5544 else if(this.line.prev)
5546 EditLine line = this.line.prev;
5550 _DelCh(line, this.y-1, x, this.line, this.y, this.x, true, false, null);
5558 this.selLine = this.line;
5563 SetViewToCursor(true);
5568 Emptyline(this.line,this.y);
5569 this.selX = this.x = 0;
5572 this.selLine = this.line;
5574 SetViewToCursor(true);
5584 SetViewToCursor(true);
5592 SetViewToCursor(true);
5596 bool GoToLineNum(int lineNum)
5601 EditLine line = this.lines.first;
5602 for(c = 0; c < lineNum && line; c++, line = line.next);
5612 SetViewToCursor(true);
5619 // NOTE: Mismatch with NotifyCaretMove() for x/y + 1
5620 bool GoToPosition(EditLine line, int y, int x)
5632 for(line = this.lines.first, c = 0; c<y && line; c++, line = line.next);
5646 SetViewToCursor(true);
5653 void SetViewToCursor(bool setCaret)
5663 bool dontScroll = false;
5669 selected = selX != this.x || selY != y;
5676 checkLine = dropLine;
5682 checkLine = this.line;
5687 numLines = clientSize.h / space.h;
5689 // This is broken. The EditBox now doesn't do anything different when adding to it,
5690 // regardless of the previous scrolling position. It should be read and then set again
5691 // if one wishes to preserve it.
5692 /* // Don't scroll view to cursor if we're in a EES_NOCARET box
5693 if(style.noCaret && this.viewY < lineCount - numLines - 1)
5697 // Horizontal Adjustment
5698 if(!dontScroll && checkLine)
5702 dropX = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5705 this.x = AdjustXPosition(this.line, MAXINT, true, &x, checkX, 0);
5711 if(x + space.w >= this.viewX + clientSize.w && clientSize.w >= space.w)
5712 viewX = x - clientSize.w+space.w;
5713 if(x < this.viewX + clientSize.w/2 - space.w)
5714 viewX = Max(0, x - clientSize.w/2 + space.w);
5722 // Vertical Adjustment
5723 if(viewY > checkY) viewY = checkY;
5724 if(viewY + numLines <= checkY)
5726 if(clientSize.h >= space.h)
5728 for(line = viewLine; line && (viewY + numLines <= checkY); line = line.next)
5738 for(;dropLine && dropLine.prev && dropY >= numLines;)
5740 dropLine = dropLine.prev;
5744 for(;this.line && this.line.prev && this.y >= numLines;)
5746 this.line = this.line.prev;
5751 SetScrollPosition(viewX, viewY * this.space.h);
5753 UpdateCaretPosition(setCaret);
5759 selLine = this.line;
5769 void CenterOnCursor()
5771 int numLines = clientSize.h / this.space.h;
5772 int y = this.y - numLines / 2;
5774 bool figureSyntax = false;
5775 EditLine oldViewLine = viewLine;
5776 if(y > this.lineCount - numLines) y = this.lineCount-numLines;
5781 for(;y < this.viewY; y++)
5783 this.viewLine = this.viewLine.prev;
5784 style.recomputeSyntax = true;
5786 for(;y > this.viewY; y--)
5788 this.viewLine = this.viewLine.next;
5789 figureSyntax = true;
5792 FigureStartSyntaxStates(oldViewLine, false);
5796 SetScrollPosition(this.viewX, viewY * this.space.h);
5797 UpdateCaretPosition(true);
5801 void SetCursorToView()
5812 numLines = clientSize.h / this.space.h;
5816 for(c=0, line = this.viewLine.next; line && c<numLines && (this.viewY < this.lineCount - numLines); line = line.next, c++);
5817 SetScrollPosition(this.viewX, (this.viewY + c) * this.space.h);
5821 EditLine oldLine = this.line;
5822 bool lastOne = false;
5823 EditLine oldViewLine = this.viewLine;
5824 bool figureSyntax = false;
5826 if(this.y >= this.lineCount-1) return;
5828 for(c=0, line = this.line.next; line && c<numLines; line = line.next, c++)
5830 if(this.viewY + numLines < this.lines.count)
5832 this.viewLine = this.viewLine.next;
5834 figureSyntax = true;
5836 else if(c && !lastOne)
5845 FigureStartSyntaxStates(oldViewLine, false);
5846 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5849 SetViewToCursor(false);
5858 if(this.y == 0) return;
5860 numLines = clientSize.h / this.space.h;
5864 for(c=0, line = this.viewLine.prev; line && c<numLines; line = line.prev, c++);
5865 SetScrollPosition(this.viewX, (this.viewY - c) * this.space.h);
5869 EditLine oldLine = this.line;
5871 for(c=0, line = this.line.prev; line && c<numLines; line = line.prev, c++)
5876 if(this.viewLine.prev)
5878 this.viewLine = this.viewLine.prev;
5880 style.recomputeSyntax = true;
5884 this.x = AdjustXPosition(line, caretX, true, null, MAXINT, 0);
5887 SetViewToCursor(false);
5894 if(this.viewLine.prev)
5897 // this.viewLine = this.viewLine.prev;
5900 SetScrollPosition(this.viewX, (this.viewY - 1) * this.space.h);
5907 if(this.viewLine.next)
5910 // this.viewLine = this.viewLine.next;
5913 SetScrollPosition(this.viewX, (this.viewY + 1) * this.space.h);
5920 EditLine l1, l2, line;
5921 int x1, x2, nx1, nx2;
5926 if(!this.selLine) return 0;
5927 if(this.selLine == this.line && this.selX == this.x) return 0;
5928 if(this.selY < this.y)
5935 else if(this.selY > this.y)
5942 else if(this.selX < this.x)
5944 l1 = l2 = this.line;
5950 l1 = l2 = this.line;
5954 nx1 = Min(x1,l1.count);
5955 nx2 = Min(x2,l2.count);
5957 // Find Number of Bytes Needed
5959 for(line = l1; line; line = line.next)
5961 if(line == l1) start = nx1; else start = 0;
5962 if(line == l2) end = nx2; else end = line.count;
5964 if(style.freeCaret && line == l2)
5967 count = Max(x2-Max(x1,l1.count),0);
5969 count = Max(x2-l2.count,0);
5973 if(line == l2) break;
5974 // Add Carriage Return / line Feed
5981 void GetSelPos(EditLine * l1, int *y1, int *x1, EditLine * l2, int * y2, int * x2, bool reorder)
5985 if(!reorder || this.selY < this.y)
5987 if(l1) *l1 = this.selLine;
5988 if(y1) *y1 = this.selY;
5989 if(x1) *x1 = this.selX;
5990 if(l2) *l2 = this.line;
5991 if(y2) *y2 = this.y;
5992 if(x2) *x2 = this.x;
5994 else if(this.selY > this.y)
5996 if(l1) *l1 = this.line;
5997 if(y1) *y1 = this.y;
5998 if(x1) *x1 = this.x;
5999 if(l2) *l2 = this.selLine;
6000 if(y2) *y2 = this.selY;
6001 if(x2) *x2 = this.selX;
6003 else if(this.selX < this.x)
6005 if(l1) *l1 = this.line;
6006 if(y1) *y1 = this.selY;
6007 if(x1) *x1 = this.selX;
6008 if(l2) *l2 = this.line;
6009 if(y2) *y2 = this.y;
6010 if(x2) *x2 = this.x;
6014 if(l1) *l1 = this.line;
6015 if(y1) *y1 = this.y;
6016 if(x1) *x1 = this.x;
6017 if(l2) *l2 = this.line;
6018 if(y2) *y2 = this.selY;
6019 if(x2) *x2 = this.selX;
6024 void SetSelPos(EditLine l1, int y1, int x1, EditLine l2, int y2, int x2)
6026 if(this && (this.selY != y1 || this.selX != x1 || this.y != y2 || this.x != x2))
6028 this.selLine = (EditLine)l1;
6031 this.line = (EditLine)l2;
6035 SetViewToCursor(true);
6039 int GetText(char * text, EditLine _l1, int _y1, int _x1, EditLine _l2, int _y2, int _x2, bool addCr, bool addSpaces)
6041 EditLine l1, l2, line;
6042 int x1, x2, nx1, nx2;
6073 nx1 = Min(x1,l1.count);
6074 nx2 = Min(x2,l2.count);
6077 for(line = l1; line; line = line.next)
6079 if(line == l1) start = nx1; else start = 0;
6080 if(line == l2) end = nx2; else end = line.count;
6083 CopyBytes(text, line.buffer + start, end - start);
6086 numChars += end-start;
6088 if(style.freeCaret && line == l2 && addSpaces)
6091 count = Max(x2-Max(x1,l1.count),0);
6093 count = Max(x2-l2.count,0);
6096 FillBytes(text,' ',count);
6102 if(line == l2) break;
6114 // '\0' terminate Terminate
6121 void GetSel(char * text, bool addCr)
6123 GetText(text, line, y, x, selLine, selY, selX, addCr, true);
6126 private void _Deselect() // This assumes marking lines as dirty is handled by the caller
6144 int size = SelSize();
6147 // Try to allocate memory
6148 ClipBoard clipBoard { };
6149 if(clipBoard.Allocate(size+1))
6151 GetSel(clipBoard.memory, true);
6164 ClipBoard clipBoard { };
6165 if(clipBoard.Load())
6166 PutS(clipBoard.memory);
6178 SetViewToCursor(true);
6184 void DeleteSelection()
6189 SetViewToCursor(true);
6195 void Save(File f, bool cr)
6198 savedAction = undoBuffer.curAction;
6200 for(line = this.lines.first; line; line = line.next)
6202 f.Write(line.buffer, line.count, 1);
6205 if(cr) f.Putc('\r');
6211 #define BUFFER_SIZE 16384
6214 undoBuffer.dontRecord++;
6217 char buffer[BUFFER_SIZE];
6221 int count = f.Read(buffer, 1, BUFFER_SIZE-1);
6222 buffer[count] = '\0';
6228 undoBuffer.dontRecord--;
6229 undoBuffer.count = 0;
6230 undoBuffer.curAction = 0;
6231 itemEditUndo.disabled = undoBuffer.curAction == 0;
6232 itemEditRedo.disabled = undoBuffer.curAction == undoBuffer.count;
6235 EditBoxFindResult Find(char * text, bool matchWord, bool matchCase, bool isSearchDown)
6239 bool firstPass = true;
6240 EditBoxFindResult result = found;
6242 if(!this.line) return notFound;
6245 for(line = this.line;;)
6253 line = this.lines.first;
6259 line = this.lines.last;
6260 num = this.lineCount - 1;
6266 string = SearchString(line.buffer, firstPass ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6268 string = RSearchString(line.buffer,text,firstPass ? (Min(this.x,line.count)-1) : line.count,matchCase,matchWord);
6272 Select((void *)line,num,string - line.buffer,(void *)line,num,string - line.buffer + strlen(text));
6275 if(line == this.line && !firstPass) break;
6293 EditBoxFindResult FindInSelection(char * text, bool matchWord, bool matchCase, EditLine l2, int y2, int x2)
6297 int searchLen = strlen(text);
6298 for(line = (EditLine)this.line, y = this.y; y <= y2; line = line.next, y++)
6300 char * string = SearchString(line.buffer, (y == this.y) ? Min(this.x,line.count) : 0,text,matchCase,matchWord);
6301 if(string && (y < y2 || (string - line.buffer) + searchLen <= x2))
6303 Select((void *)line,y,string - line.buffer,(void *)line,y,string - line.buffer + strlen(text));
6310 bool OnKeyDown(Key key, unichar ch)
6313 //PrintLn("OnKeyDown: code = ", key.code, ", mods = ", key.modifiers, ", ch = ", (int)ch);
6315 if(!NotifyKeyDown(master, this, key, ch))
6325 this.mouseMove = false;
6326 OnLeftButtonUp(0,0,0);
6327 SetViewToCursor(true);
6337 public class EditBoxStream : File
6340 BufferLocation start, sel;
6347 EditBox editBox = this.editBox;
6349 editBox.x = start.x;
6350 editBox.y = start.y;
6351 editBox.line = start.line;
6353 editBox.selX = sel.x;
6354 editBox.selY = sel.y;
6355 editBox.selLine = sel.line;
6357 editBox.SetViewToCursor(true);
6358 //editBox.ComputeColumn();
6362 property EditBox editBox
6370 start.line = value.line;
6374 sel.line = value.selLine;
6377 value.GoToHome(true);
6380 get { return editBox; }
6383 uint Read(byte * buffer, uint size, uint count)
6386 EditBox editBox = this.editBox;
6387 EditLine line = editBox.line;
6393 for(;read < count && line; line = (*&line.next))
6395 int numBytes = Min(count - read, (*&line.count) - x);
6398 memcpy(buffer + read, (*&line.buffer) + x, numBytes);
6403 for(;read < count && x < (*&line.count);)
6405 buffer[read++] = (*&line.buffer)[x++];
6412 buffer[read++] = '\n';
6417 if(x == (*&line.count) && (*&line.next))
6426 editBox.line = editBox.selLine = line;
6427 editBox.x = editBox.selX = x;
6428 editBox.y = editBox.selY = y;
6433 bool Seek(int pos, FileSeekMode mode)
6436 EditBox editBox = this.editBox;
6437 EditLine line = editBox.line;
6439 if(mode == FileSeekMode::start)
6441 pos = pos - this.pos;
6451 for(;read < pos && line; line = (*&line.next))
6453 int numBytes = Min(pos - read, (*&line.count) - x);
6456 read += numBytes; x += numBytes;
6459 /*for(;read < pos && x < (*&line.count);)
6487 for(;read < pos && line; line = (*&line.prev))
6489 int numBytes = Min(pos - read, x);
6495 /*for(;read < pos && x > 0;)
6505 x = (*&(*&line.prev).count);
6521 editBox.line = editBox.selLine = line;
6522 editBox.x = editBox.selX = x;
6523 editBox.y = editBox.selY = y;
6528 bool Puts(char * string)
6530 EditBox editBox = this.editBox;
6531 BufferLocation start { editBox.line, editBox.y, editBox.x };
6535 editBox.AddS(string);
6537 pos.line = editBox.line;
6541 this.start.AdjustAdd(start, pos);
6542 sel.AdjustAdd(start, pos);
6547 // NOTE: BYTE, NOT UNICODE CHARACTER!
6550 EditBox editBox = this.editBox;
6551 BufferLocation start = { editBox.line, editBox.y, editBox.x };
6556 utf8Bytes[numBytes++] = ch;
6557 utf8Bytes[numBytes] = 0;
6558 if(UTF8Validate(utf8Bytes))
6560 editBox.AddCh(UTF8_GET_CHAR(utf8Bytes, numBytes));
6562 pos.line = editBox.line;
6565 this.start.AdjustAdd(start, pos);
6566 sel.AdjustAdd(start, pos);
6573 bool Getc(char * ch)
6575 return Read(ch, 1, 1) ? true : false;
6578 void DeleteBytes(uint count)
6580 EditBox editBox = this.editBox;
6583 BufferLocation pos { editBox.line, editBox.y, editBox.x };
6584 BufferLocation end = pos;
6589 for(;c < count && end.line && end.x < end.line.count;)
6594 if(c < count && end.line && end.line.next)
6596 end.line = end.line.next;
6605 start.AdjustDelete(pos, end);
6606 sel.AdjustDelete(pos, end);
6608 editBox._DelCh(pos.line, pos.y, pos.x, end.line, end.y, end.x, true, false, null);