Procházet zdrojové kódy

Merge pull request #2229 from kallisto56/master

Feature: Multi-Cursors
Brian Fiete před 3 měsíci
rodič
revize
ba4d29d28d

+ 92 - 2
BeefLibs/Beefy2D/src/theme/dark/DarkEditWidget.bf

@@ -243,9 +243,9 @@ namespace Beefy.theme.dark
 			return mLineCoords[anchorLine + 1] == mLineCoords[checkLine + 1];
 			return mLineCoords[anchorLine + 1] == mLineCoords[checkLine + 1];
 		}
 		}
 
 
-		protected override void AdjustCursorsAfterExternalEdit(int index, int ofs)
+		protected override void AdjustCursorsAfterExternalEdit(int index, int ofs, int lineOfs)
  		{
  		{
-			 base.AdjustCursorsAfterExternalEdit(index, ofs);
+			 base.AdjustCursorsAfterExternalEdit(index, ofs, lineOfs);
 			 mWantsCheckScrollPosition = true;
 			 mWantsCheckScrollPosition = true;
 		}
 		}
 
 
@@ -775,6 +775,96 @@ namespace Beefy.theme.dark
 				}
 				}
             }
             }
 
 
+			if (mEditWidget.mHasFocus)
+			{
+				void DrawSelection(int line, int startColumn, int endColumn)
+				{
+					float x = startColumn * mCharWidth;
+					float y = mLineCoords[line];
+					float width = Math.Abs(startColumn - endColumn) * mCharWidth;
+					float height = mLineCoords[line + 1] - y;
+
+					using (g.PushColor(mHiliteColor))
+						g.FillRect(x, y, width, height);
+				}
+
+				void DrawSelection()
+				{
+					if (!HasSelection())
+						return;
+
+					mSelection.Value.GetAsForwardSelect(var startPos, var endPos);
+					GetLineColumnAtIdx(startPos, var startLine, var startColumn);
+					GetLineColumnAtIdx(endPos, var endLine, var endColumn);
+
+					// Selection is on the single line
+					if (startLine == endLine)
+					{
+						DrawSelection(startLine, startColumn, endColumn);
+						return;
+					}
+
+					// Selection goes across multiple lines
+
+					// First line
+					GetLinePosition(startLine, ?, var firstLineEndIdx);
+					GetLineColumnAtIdx(firstLineEndIdx, ?, var firstLineEndColumn);
+					DrawSelection(startLine, startColumn, firstLineEndColumn + 1);
+
+					for (var lineIdx = startLine + 1; lineIdx < endLine; lineIdx++)
+					{
+						GetLinePosition(lineIdx, var lineStart, var lineEnd);
+						GetLineColumnAtIdx(lineEnd, var line, var column);
+
+						if (column == 0)
+						{
+							// Blank line selected
+							var y = mLineCoords[line];
+							var height = mLineCoords[line + 1] - y;
+
+							using (g.PushColor(mHiliteColor))
+								g.FillRect(0, y, 4, height);
+						}
+						else
+						{
+							DrawSelection(line, 0, column + 1);
+						}
+					}
+
+					// Last line
+					DrawSelection(endLine, 0, endColumn);
+				}
+
+				var prevTextCursor = mCurrentTextCursor;
+				for (var cursor in mTextCursors)
+				{
+					if (cursor.mId == 0)
+						continue;
+
+					SetTextCursor(cursor);
+					DrawSelection();
+
+					float x = 0;
+					float y = 0;
+					if (cursor.mVirtualCursorPos.HasValue)
+					{
+						x = cursor.mVirtualCursorPos.Value.mColumn * mCharWidth;
+						y = mLineCoords[cursor.mVirtualCursorPos.Value.mLine];
+					}
+					else
+					{
+						GetLineCharAtIdx(cursor.mCursorTextPos, var eStartLine, var eStartCharIdx);
+						GetColumnAtLineChar(eStartLine, eStartCharIdx, var column);
+						x = column * mCharWidth;
+						y = mLineCoords[eStartLine];
+					}
+
+					using (g.PushColor(0xFF80FFB3))
+						DrawCursor(x, y);
+				}
+				SetTextCursor(prevTextCursor);
+			}
+
             g.PopMatrix();
             g.PopMatrix();
 
 
 			/*using (g.PushColor(0x4000FF00))
 			/*using (g.PushColor(0x4000FF00))

+ 39 - 0
BeefLibs/Beefy2D/src/utils/UndoManager.bf

@@ -138,8 +138,47 @@ namespace Beefy.utils
 			mCurCost = 0;
 			mCurCost = 0;
         }
         }
 
 
+		bool TryMerge(UndoAction action)
+		{
+			var currentBatchEnd = action as UndoBatchEnd;
+			if (currentBatchEnd == null)
+				return false;
+
+			var currentBatchStart = currentBatchEnd.mBatchStart as UndoBatchStart;
+			var prevBatchEndIdx = mUndoList.IndexOf(currentBatchStart) - 1;
+			if (prevBatchEndIdx <= 0)
+				return false;
+
+			var prevBatchEnd = mUndoList[prevBatchEndIdx] as UndoBatchEnd;
+			if (prevBatchEnd == null)
+				return false;
+
+			var prevBatchStart = prevBatchEnd.mBatchStart as UndoBatchStart;
+			if (prevBatchStart == null)
+				return false;
+			if (prevBatchStart.Merge(currentBatchStart) == false)
+				return false;
+
+			mUndoList.Remove(currentBatchStart);
+			mUndoList.Remove(currentBatchEnd);
+
+			mUndoList.Remove(prevBatchEnd);
+			mUndoList.Add(prevBatchEnd);
+
+			delete currentBatchStart;
+			delete currentBatchEnd;
+
+			mUndoIdx = (.)mUndoList.Count;
+
+			Debug.WriteLine("SUCCESS: Merged");
+			return true;
+		}
+
         public void Add(UndoAction action, bool allowMerge = true)
         public void Add(UndoAction action, bool allowMerge = true)
         {
         {
+			if ((allowMerge) && (TryMerge(action)))
+				return;
+
 			if (mFreezeDeletes == 0)
 			if (mFreezeDeletes == 0)
 				mCurCost += action.GetCost();
 				mCurCost += action.GetCost();
 			if (action is IUndoBatchStart)
 			if (action is IUndoBatchStart)

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 854 - 62
BeefLibs/Beefy2D/src/widgets/EditWidget.bf


+ 2 - 0
IDE/src/Commands.bf

@@ -340,6 +340,8 @@ namespace IDE
 			Add("Zoom Out", new => gApp.Cmd_ZoomOut);
 			Add("Zoom Out", new => gApp.Cmd_ZoomOut);
 			Add("Zoom Reset", new => gApp.Cmd_ZoomReset);
 			Add("Zoom Reset", new => gApp.Cmd_ZoomReset);
 			Add("Attach to Process", new => gApp.[Friend]DoAttach);
 			Add("Attach to Process", new => gApp.[Friend]DoAttach);
+			Add("Select Next Match", new => gApp.Cmd_SelectNextMatch);
+			Add("Skip Current Match and Select Next", new => gApp.Cmd_SkipCurrentMatchAndSelectNext);
 
 
 			Add("Test Enable Console", new => gApp.Cmd_TestEnableConsole);
 			Add("Test Enable Console", new => gApp.Cmd_TestEnableConsole);
 		}
 		}

+ 47 - 25
IDE/src/IDEApp.bf

@@ -4300,6 +4300,7 @@ namespace IDE
 			var sourceViewPanel = GetActiveSourceViewPanel();
 			var sourceViewPanel = GetActiveSourceViewPanel();
 			if (sourceViewPanel != null)
 			if (sourceViewPanel != null)
 			{
 			{
+				sourceViewPanel.EditWidget.Content.RemoveSecondaryTextCursors();
 				sourceViewPanel.GotoLine();
 				sourceViewPanel.GotoLine();
 				return;
 				return;
 			}
 			}
@@ -5479,7 +5480,7 @@ namespace IDE
 				if (ewc.HasSelection())
 				if (ewc.HasSelection())
 					ewc.GetSelectionText(debugExpr);
 					ewc.GetSelectionText(debugExpr);
 				else
 				else
-					sourceViewPanel.GetDebugExpressionAt(ewc.CursorTextPos, debugExpr);
+					sourceViewPanel.GetDebugExpressionAt(ewc.mTextCursors.Front.mCursorTextPos, debugExpr);
 				dialog.Init(debugExpr);
 				dialog.Init(debugExpr);
 			}
 			}
 			else if (let immediatePanel = activePanel as ImmediatePanel)
 			else if (let immediatePanel = activePanel as ImmediatePanel)
@@ -5961,6 +5962,18 @@ namespace IDE
 			ToggleCheck(ideCommand.mMenuItem, ref mTestEnableConsole);
 			ToggleCheck(ideCommand.mMenuItem, ref mTestEnableConsole);
 		}
 		}
 
 
+		[IDECommand]
+		public void Cmd_SelectNextMatch()
+		{
+			GetActiveSourceEditWidgetContent()?.SelectNextMatch();
+		}
+
+		[IDECommand]
+		public void Cmd_SkipCurrentMatchAndSelectNext()
+		{
+			GetActiveSourceEditWidgetContent()?.SkipCurrentMatchAndSelectNext();
+		}
+
 		public void UpdateMenuItem_HasActivePanel(IMenu menu)
 		public void UpdateMenuItem_HasActivePanel(IMenu menu)
 		{
 		{
 			menu.SetDisabled(GetActivePanel() == null);
 			menu.SetDisabled(GetActivePanel() == null);
@@ -7862,6 +7875,7 @@ namespace IDE
 					if (!sourceViewPanel.[Friend]mWantsFullRefresh)
 					if (!sourceViewPanel.[Friend]mWantsFullRefresh)
 						sourceViewPanel.UpdateQueuedEmitShowData();
 						sourceViewPanel.UpdateQueuedEmitShowData();
 
 
+					sourceViewPanel.EditWidget?.Content.RemoveSecondaryTextCursors();
 					return sourceViewPanel;
 					return sourceViewPanel;
 				}
 				}
 			}
 			}
@@ -7873,6 +7887,7 @@ namespace IDE
 				svTabButton.mIsTemp = true;
 				svTabButton.mIsTemp = true;
 			sourceViewPanel.ShowHotFileIdx(showHotIdx);
 			sourceViewPanel.ShowHotFileIdx(showHotIdx);
 			sourceViewPanel.ShowFileLocation(refHotIdx, Math.Max(0, line), Math.Max(0, column), hilitePosition);
 			sourceViewPanel.ShowFileLocation(refHotIdx, Math.Max(0, line), Math.Max(0, column), hilitePosition);
+			sourceViewPanel.EditWidget?.Content.RemoveSecondaryTextCursors();
 			return sourceViewPanel;
 			return sourceViewPanel;
 		}
 		}
 
 
@@ -8768,38 +8783,45 @@ namespace IDE
 				return;
 				return;
 
 
 			var ewc = sourceViewPanel.mEditWidget.mEditWidgetContent;
 			var ewc = sourceViewPanel.mEditWidget.mEditWidgetContent;
-			if (!ewc.HasSelection())
-				return;
-
-			/*ewc.mSelection.Value.GetAsForwardSelect(var startPos, var endPos);
-			for (int i = startPos; i < endPos; i++)
+			for (var cursor in ewc.mTextCursors)
 			{
 			{
-				var c = ref ewc.mData.mText[i].mChar;
-				if (toUpper)
-					c = c.ToUpper;
-				else
-					c = c.ToLower;
-			}*/
+				ewc.SetTextCursor(cursor);
+				if (!ewc.HasSelection())
+					continue;
 
 
-			var prevSel = ewc.mSelection.Value;
+				/*ewc.mSelection.Value.GetAsForwardSelect(var startPos, var endPos);
+				for (int i = startPos; i < endPos; i++)
+				{
+					var c = ref ewc.mData.mText[i].mChar;
+					if (toUpper)
+						c = c.ToUpper;
+					else
+						c = c.ToLower;
+				}*/
 
 
-			var str = scope String();
-			ewc.GetSelectionText(str);
+				var prevSel = ewc.mSelection.Value;
 
 
-			var prevStr = scope String();
-			prevStr.Append(str);
+				var str = scope String();
+				ewc.GetSelectionText(str);
 
 
-			if (toUpper)
-				str.ToUpper();
-			else
-				str.ToLower();
+				var prevStr = scope String();
+				prevStr.Append(str);
 
 
-			if (str == prevStr)
-				return;
+				if (toUpper)
+					str.ToUpper();
+				else
+					str.ToLower();
 
 
-			ewc.InsertAtCursor(str);
+				if (str == prevStr)
+					continue;
+
+				ewc.CreateMultiCursorUndoBatch("IDEApp.ChangeCase()");
+				ewc.InsertAtCursor(str);
 
 
-			ewc.mSelection = prevSel;
+				ewc.mSelection = prevSel;
+			}
+			ewc.CloseMultiCursorUndoBatch();
+			ewc.SetPrimaryTextCursor();
 		}
 		}
 
 
 		public bool IsFilteredOut(String fileName)
 		public bool IsFilteredOut(String fileName)

+ 173 - 110
IDE/src/ui/AutoComplete.bf

@@ -995,7 +995,7 @@ namespace IDE.ui
 
 
 				List<StringView> textSections = scope List<StringView>(selectedEntry.mText.Split('\x01'));
 				List<StringView> textSections = scope List<StringView>(selectedEntry.mText.Split('\x01'));
 
 
-				int cursorPos = mAutoComplete.mTargetEditWidget.Content.CursorTextPos;
+				int cursorPos = mAutoComplete.mTargetEditWidget.Content.mTextCursors.Front.mCursorTextPos;
 				for (int sectionIdx = 0; sectionIdx < mAutoComplete.mInvokeSrcPositions.Count - 1; sectionIdx++)
 				for (int sectionIdx = 0; sectionIdx < mAutoComplete.mInvokeSrcPositions.Count - 1; sectionIdx++)
 				{
 				{
 				    if (cursorPos > mAutoComplete.mInvokeSrcPositions[sectionIdx])
 				    if (cursorPos > mAutoComplete.mInvokeSrcPositions[sectionIdx])
@@ -1445,7 +1445,7 @@ namespace IDE.ui
 
 
             if ((mInvokeWindow != null) && (!mInvokeWidget.mIsAboveText))
             if ((mInvokeWindow != null) && (!mInvokeWidget.mIsAboveText))
             {
             {
-                int textIdx = mTargetEditWidget.Content.CursorTextPos;
+                int textIdx = mTargetEditWidget.Content.mTextCursors.Front.mCursorTextPos;
                 int line = 0;
                 int line = 0;
                 int column = 0;
                 int column = 0;
                 if (textIdx >= 0)
                 if (textIdx >= 0)
@@ -1563,7 +1563,11 @@ namespace IDE.ui
 		{
 		{
 			if ((mInsertEndIdx != -1) && (mInsertStartIdx != -1))
 			if ((mInsertEndIdx != -1) && (mInsertStartIdx != -1))
 			{
 			{
-				mTargetEditWidget.Content.ExtractString(mInsertStartIdx, Math.Max(mInsertEndIdx - mInsertStartIdx, 0), outFilter);
+				var length = Math.Abs(mInsertEndIdx - mInsertStartIdx);
+				if (length == 0)
+					return;
+				var start = Math.Min(mInsertStartIdx, mInsertEndIdx);
+				mTargetEditWidget.Content.ExtractString(start, length, outFilter);
 			}
 			}
 		}
 		}
 
 
@@ -1571,13 +1575,33 @@ namespace IDE.ui
         {
         {
 			//Debug.WriteLine("GetAsyncTextPos start {0} {1}", mInsertStartIdx, mInsertEndIdx);
 			//Debug.WriteLine("GetAsyncTextPos start {0} {1}", mInsertStartIdx, mInsertEndIdx);
 
 
-            mInsertEndIdx = (int32)mTargetEditWidget.Content.CursorTextPos;
-			while ((mInsertStartIdx != -1) && (mInsertStartIdx < mInsertEndIdx))
+            mInsertEndIdx = (int32)mTargetEditWidget.Content.mTextCursors.Front.mCursorTextPos;
+			/*while ((mInsertStartIdx != -1) && (mInsertStartIdx < mInsertEndIdx))
 			{
 			{
 				char8 c = (char8)mTargetEditWidget.Content.mData.mText[mInsertStartIdx].mChar;
 				char8 c = (char8)mTargetEditWidget.Content.mData.mText[mInsertStartIdx].mChar;
-				if ((c != ' ') && (c != ','))
+				Debug.WriteLine("StartIdx: {}, EndIdx: {}, mData.mText[startIdx]: '{}'", mInsertStartIdx, mInsertEndIdx, c);
+				if ((c != ' ') && (c != ',') && (c != '('))
 				    break;
 				    break;
 				mInsertStartIdx++;
 				mInsertStartIdx++;
+			}*/
+			mInsertStartIdx = mInsertEndIdx-1;
+			while ((mInsertStartIdx <= mInsertEndIdx) && (mInsertStartIdx > 0))
+			{
+				var data = mTargetEditWidget.Content.mData.mText[mInsertStartIdx - 1];
+				var type = (SourceElementType)data.mDisplayTypeId;
+				var char = data.mChar;
+
+				// Explicit delimeters
+				if ((char == '\n') || (char == '}') || (char == ';') || (char == '.'))
+					break;
+
+				if (char.IsWhiteSpace)
+					break;
+
+				if ((!char.IsLetterOrDigit) && (char != '_') && (type != .Keyword) && (!char.IsWhiteSpace) && (data.mChar != '@'))
+					break;
+
+				mInsertStartIdx--;
 			}
 			}
 
 
             /*mInsertStartIdx = mInsertEndIdx;
             /*mInsertStartIdx = mInsertEndIdx;
@@ -2402,6 +2426,11 @@ namespace IDE.ui
 					}
 					}
 					continue;
 					continue;
 				}
 				}
+				else if (entryDisplay.Length == 0)
+				{
+					// Skip entry that has no name
+					continue;
+				}
 
 
                 switch (entryType)
                 switch (entryType)
                 {
                 {
@@ -2549,7 +2578,7 @@ namespace IDE.ui
                 }*/
                 }*/
             }
             }
             
             
-            if (changedAfterInfo)
+            if ((changedAfterInfo) || (mTargetEditWidget.Content.mTextCursors.Count > 1))
             {
             {
                 GetAsyncTextPos();                
                 GetAsyncTextPos();                
             }
             }
@@ -2561,7 +2590,7 @@ namespace IDE.ui
                 mTargetEditWidget.Content.GetLineCharAtIdx(mInvokeSrcPositions[0], out invokeLine, out invokeColumn);
                 mTargetEditWidget.Content.GetLineCharAtIdx(mInvokeSrcPositions[0], out invokeLine, out invokeColumn);
                 int insertLine = 0;
                 int insertLine = 0;
                 int insertColumn = 0;
                 int insertColumn = 0;
-                mTargetEditWidget.Content.GetLineCharAtIdx(mTargetEditWidget.Content.CursorTextPos, out insertLine, out insertColumn);
+                mTargetEditWidget.Content.GetLineCharAtIdx(mTargetEditWidget.Content.mTextCursors.Front.mCursorTextPos, out insertLine, out insertColumn);
 
 
                 if ((insertLine != invokeLine) && ((insertLine - invokeLine) * gApp.mCodeFont.GetHeight() < GS!(40)))
                 if ((insertLine != invokeLine) && ((insertLine - invokeLine) * gApp.mCodeFont.GetHeight() < GS!(40)))
                     mInvokeWidget.mIsAboveText = true;
                     mInvokeWidget.mIsAboveText = true;
@@ -2569,7 +2598,7 @@ namespace IDE.ui
 
 
 			mInfoFilter = new String();
 			mInfoFilter = new String();
 			GetFilter(mInfoFilter);
 			GetFilter(mInfoFilter);
-            UpdateData(selectString, changedAfterInfo);
+			UpdateData(selectString, changedAfterInfo);
 			mPopulating = false;
 			mPopulating = false;
 
 
 			//Debug.WriteLine("SetInfo {0} {1}", mInsertStartIdx, mInsertEndIdx);
 			//Debug.WriteLine("SetInfo {0} {1}", mInsertStartIdx, mInsertEndIdx);
@@ -2720,7 +2749,7 @@ namespace IDE.ui
 
 
 			var targetSourceEditWidgetContent = mTargetEditWidget.Content as SourceEditWidgetContent;
 			var targetSourceEditWidgetContent = mTargetEditWidget.Content as SourceEditWidgetContent;
 			var sourceEditWidgetContent = targetSourceEditWidgetContent;
 			var sourceEditWidgetContent = targetSourceEditWidgetContent;
-			var prevCursorPosition = sourceEditWidgetContent.CursorTextPos;
+			var prevCursorPosition = sourceEditWidgetContent.mTextCursors.Front.mCursorTextPos;
 			var prevScrollPos = mTargetEditWidget.mVertPos.mDest;
 			var prevScrollPos = mTargetEditWidget.mVertPos.mDest;
 
 
 			UndoBatchStart undoBatchStart = null;
 			UndoBatchStart undoBatchStart = null;
@@ -2801,7 +2830,7 @@ namespace IDE.ui
 						return;
 						return;
 					}
 					}
 	
 	
-					sourceEditWidgetContent.CursorTextPos = fixitIdx;
+					sourceEditWidgetContent.mTextCursors.Front.mCursorTextPos = fixitIdx;
 					if (focusChange)
 					if (focusChange)
 						sourceEditWidgetContent.EnsureCursorVisible(true, true);
 						sourceEditWidgetContent.EnsureCursorVisible(true, true);
 
 
@@ -2818,7 +2847,7 @@ namespace IDE.ui
 					else
 					else
 						InsertImplText(sourceEditWidgetContent, fixitInsert);
 						InsertImplText(sourceEditWidgetContent, fixitInsert);
 
 
-					fixitIdx = (.)sourceEditWidgetContent.CursorTextPos;
+					fixitIdx = (.)sourceEditWidgetContent.mTextCursors.Front.mCursorTextPos;
 					insertCount++;
 					insertCount++;
 				}
 				}
 			}
 			}
@@ -2826,9 +2855,9 @@ namespace IDE.ui
 			if (!focusChange)
 			if (!focusChange)
 			{
 			{
 				mTargetEditWidget.VertScrollTo(prevScrollPos, true);
 				mTargetEditWidget.VertScrollTo(prevScrollPos, true);
-				sourceEditWidgetContent.CursorTextPos = prevCursorPosition;
+				sourceEditWidgetContent.mTextCursors.Front.mCursorTextPos = prevCursorPosition;
 				int addedSize = sourceEditWidgetContent.mData.mTextLength - prevTextLength;
 				int addedSize = sourceEditWidgetContent.mData.mTextLength - prevTextLength;
-				sourceEditWidgetContent.[Friend]AdjustCursorsAfterExternalEdit(fixitIdx, addedSize);
+				sourceEditWidgetContent.[Friend]AdjustCursorsAfterExternalEdit(fixitIdx, addedSize, 0);
 			}
 			}
 
 
 			if (historyEntry != null)
 			if (historyEntry != null)
@@ -2891,9 +2920,9 @@ namespace IDE.ui
 						}
 						}
 						if (!sourceEditWidgetContent.IsLineWhiteSpace(sourceEditWidgetContent.CursorLineAndColumn.mLine))
 						if (!sourceEditWidgetContent.IsLineWhiteSpace(sourceEditWidgetContent.CursorLineAndColumn.mLine))
 						{
 						{
-							int prevPos = sourceEditWidgetContent.CursorTextPos;
+							int prevPos = sourceEditWidgetContent.mTextCursors.Front.mCursorTextPos;
 							sourceEditWidgetContent.InsertAtCursor("\n");
 							sourceEditWidgetContent.InsertAtCursor("\n");
-							sourceEditWidgetContent.CursorTextPos = prevPos;
+							sourceEditWidgetContent.mTextCursors.Front.mCursorTextPos = (int32)prevPos;
 						}
 						}
 						sourceEditWidgetContent.CursorToLineEnd();
 						sourceEditWidgetContent.CursorToLineEnd();
 					}
 					}
@@ -2910,7 +2939,7 @@ namespace IDE.ui
 					}
 					}
 					else if (c == '\b') // Close block
 					else if (c == '\b') // Close block
 					{
 					{
-						int cursorPos = sourceEditWidgetContent.CursorTextPos;
+						int cursorPos = sourceEditWidgetContent.mTextCursors.Front.mCursorTextPos;
 						while (cursorPos < sourceEditWidgetContent.mData.mTextLength)
 						while (cursorPos < sourceEditWidgetContent.mData.mTextLength)
 						{
 						{
 							char8 checkC = sourceEditWidgetContent.mData.mText[cursorPos].mChar;
 							char8 checkC = sourceEditWidgetContent.mData.mText[cursorPos].mChar;
@@ -2918,7 +2947,7 @@ namespace IDE.ui
 							if (checkC == '}')
 							if (checkC == '}')
 								break;
 								break;
 						}
 						}
-						sourceEditWidgetContent.CursorTextPos = cursorPos;
+						sourceEditWidgetContent.mTextCursors.Front.mCursorTextPos = (int32)cursorPos;
 					}
 					}
 					else
 					else
 					{
 					{
@@ -2950,70 +2979,100 @@ namespace IDE.ui
 			}
 			}
 		}
 		}
 
 
-        public void InsertSelection(char32 keyChar, String insertType = null, String insertStr = null)
-        {
-			//Debug.WriteLine("InsertSelection");
+		public void InsertSelection(char32 keyChar, String insertType = null, String insertStr = null)
+		{
+			var sewc = mTargetEditWidget.Content as SourceEditWidgetContent;
+			Debug.Assert(sewc.IsPrimaryTextCursor());
+			var isExplicitInsert = (keyChar == '\0') || (keyChar == '\t') || (keyChar == '\n') || (keyChar == '\r');
 
 
-            EditSelection editSelection = EditSelection();            
-            editSelection.mStartPos = mInsertStartIdx;
-            if (mInsertEndIdx != -1)
-                editSelection.mEndPos = mInsertEndIdx;
-            else
-                editSelection.mEndPos = mInsertStartIdx;
-            
-            var entry = mAutoCompleteListWidget.mEntryList[mAutoCompleteListWidget.mSelectIdx];
-            if (keyChar == '!')
-            {
-                if (!entry.mEntryDisplay.EndsWith("!"))
-                {
-                    // Try to find one that DOES end with a '!'
-                    for (var checkEntry in mAutoCompleteListWidget.mEntryList)
-                    {
-                        if (checkEntry.mEntryDisplay.EndsWith("!"))
-                            entry = checkEntry;
-                    }
-                }
-            }
+			AutoCompleteListWidget.EntryWidget GetEntry()
+			{
+				var entry = mAutoCompleteListWidget.mEntryList[mAutoCompleteListWidget.mSelectIdx];
+				if ((keyChar == '!') && (!entry.mEntryDisplay.EndsWith("!")))
+				{
+				    // Try to find one that DOES end with a '!'
+				    for (var checkEntry in mAutoCompleteListWidget.mEntryList)
+				    {
+				        if (checkEntry.mEntryDisplay.EndsWith("!"))
+							return checkEntry;
+				    }
+				}
+
+				return entry;
+			}
+
+			EditSelection CalculateSelection(String implText)
+			{
+				var endPos = (int32)sewc.CursorTextPos;
+				var startPos = endPos;
+				var wentOverWhitespace = false;
+				while ((startPos <= endPos) && (startPos > 0))
+				{
+					var data = sewc.mData.mText[startPos - 1];
+					var type = (SourceElementType)data.mDisplayTypeId;
+
+					// Explicit delimeters
+					if ((data.mChar == '\n') || (data.mChar == '}') || (data.mChar == ';') || (data.mChar == '.'))
+						break;
+
+					var isWhiteSpace = data.mChar.IsWhiteSpace;
+					var isLetterOrDigit = data.mChar.IsLetterOrDigit;
 
 
+					// When it's not a override method for example
+					// we break right after we find a non-letter-or-digit character
+					// So we would only select last word
+					if ((implText == null) && (!isLetterOrDigit) && (data.mChar != '_') && (data.mChar != '@'))
+						break;
+
+					// This is for cases, when we are searching for 
+					if ((!isLetterOrDigit) && (type != .Keyword) && (!isWhiteSpace) && (data.mChar != '_'))
+						break;
+
+					wentOverWhitespace = isWhiteSpace;
+					startPos--;
+				}
+
+				if (wentOverWhitespace)
+					startPos++;
+
+				return EditSelection(startPos, endPos);
+			}
+
+			var entry = GetEntry();
 			if (insertStr != null)
 			if (insertStr != null)
 				insertStr.Append(entry.mEntryInsert ?? entry.mEntryDisplay);
 				insertStr.Append(entry.mEntryInsert ?? entry.mEntryDisplay);
 
 
 			if (entry.mEntryType == "fixit")
 			if (entry.mEntryType == "fixit")
 			{
 			{
 				if (insertType != null)
 				if (insertType != null)
-					insertType.Append(entry.mEntryType);            
+					insertType.Append(entry.mEntryType);
+				sewc.RemoveSecondaryTextCursors();
 				ApplyFixit(entry.mEntryInsert);
 				ApplyFixit(entry.mEntryInsert);
 				return;
 				return;
 			}
 			}
 
 
-			bool isExplicitInsert = (keyChar == '\0') || (keyChar == '\t') || (keyChar == '\n') || (keyChar == '\r');
-
-            String insertText = entry.mEntryInsert ?? entry.mEntryDisplay;
+			var insertText = scope String(entry.mEntryInsert ?? entry.mEntryDisplay);
 			if ((!isExplicitInsert) && (insertText.Contains('\t')))
 			if ((!isExplicitInsert) && (insertText.Contains('\t')))
 			{
 			{
 				// Don't insert multi-line blocks unless we have an explicit insert request (click, tab, or enter)
 				// Don't insert multi-line blocks unless we have an explicit insert request (click, tab, or enter)
 				return;
 				return;
 			}
 			}
 
 
-            if ((keyChar == '=') && (insertText.EndsWith("=")))
+			if ((keyChar == '=') && (insertText.EndsWith("=")))
 				insertText.RemoveToEnd(insertText.Length - 1);
 				insertText.RemoveToEnd(insertText.Length - 1);
-                //insertText = insertText.Substring(0, insertText.Length - 1);
-            String implText = null;
-            int tabIdx = insertText.IndexOf('\t');
-			int splitIdx = tabIdx;
-			int crIdx = insertText.IndexOf('\r');
-			if ((crIdx != -1) && (tabIdx != -1) && (crIdx < tabIdx))
-				splitIdx = crIdx;
-			if (splitIdx != -1)
-            {
-                implText = scope:: String();
-                implText.Append(insertText, splitIdx);
-                insertText.RemoveToEnd(splitIdx);
-            }
-            String prevText = scope String();
-            mTargetEditWidget.Content.ExtractString(editSelection.mStartPos, editSelection.mEndPos - editSelection.mStartPos, prevText);
 
 
-            //sAutoCompleteMRU[insertText] = sAutoCompleteIdx++;
+			// Save persistent text positions
+			PersistentTextPosition[] persistentInvokeSrcPositons = null;
+			if (mInvokeSrcPositions != null)
+			{
+				persistentInvokeSrcPositons = scope:: PersistentTextPosition[mInvokeSrcPositions.Count];
+			    for (int32 i = 0; i < mInvokeSrcPositions.Count; i++)
+			    {
+			        persistentInvokeSrcPositons[i] = new PersistentTextPosition(mInvokeSrcPositions[i]);
+			        sewc.PersistentTextPositions.Add(persistentInvokeSrcPositons[i]);
+			    }
+			}
+
 			String* keyPtr;
 			String* keyPtr;
 			int32* valuePtr;
 			int32* valuePtr;
 			if (sAutoCompleteMRU.TryAdd(entry.mEntryDisplay, out keyPtr, out valuePtr))
 			if (sAutoCompleteMRU.TryAdd(entry.mEntryDisplay, out keyPtr, out valuePtr))
@@ -3023,64 +3082,68 @@ namespace IDE.ui
 			}
 			}
 			*valuePtr = sAutoCompleteIdx++;
 			*valuePtr = sAutoCompleteIdx++;
 
 
-            if (insertText == prevText)
-                return;
+			String implText = null;
+			int tabIdx = insertText.IndexOf('\t');
+			int splitIdx = tabIdx;
+			int crIdx = insertText.IndexOf('\r');
+			if ((crIdx != -1) && (tabIdx != -1) && (crIdx < tabIdx))
+				splitIdx = crIdx;
+			if (splitIdx != -1)
+			{
+			    implText = scope:: String();
+			    implText.Append(insertText, splitIdx);
+			    insertText.RemoveToEnd(splitIdx);
+			}
 
 
-            var sourceEditWidgetContent = mTargetEditWidget.Content as SourceEditWidgetContent;
+			for (var cursor in sewc.mTextCursors)
+			{
+				sewc.SetTextCursor(cursor);
+				var editSelection = CalculateSelection(implText);
 
 
-            PersistentTextPosition[] persistentInvokeSrcPositons = null;
-            if (mInvokeSrcPositions != null)
-            {
-                persistentInvokeSrcPositons = scope:: PersistentTextPosition[mInvokeSrcPositions.Count];
-                for (int32 i = 0; i < mInvokeSrcPositions.Count; i++)
-                {
-                    persistentInvokeSrcPositons[i] = new PersistentTextPosition(mInvokeSrcPositions[i]);
-                    sourceEditWidgetContent.PersistentTextPositions.Add(persistentInvokeSrcPositons[i]);
-                }
-            }
+				var prevText = scope String();
+				sewc.ExtractString(editSelection.MinPos, editSelection.Length, prevText);
+				if ((prevText.Length > 0) && (insertText == prevText))
+					continue;
 
 
-            mTargetEditWidget.Content.mSelection = editSelection;
-            //bool isMethod = (entry.mEntryType == "method");
-            if (insertText.EndsWith("<>"))
-            {
-                if (keyChar == '\t')
-                    mTargetEditWidget.Content.InsertCharPair(insertText);
-                else if (keyChar == '<')
+				sewc.mSelection = editSelection;
+				sewc.mCursorTextPos = (int32)editSelection.MaxPos;
+
+				if (insertText.EndsWith("<>"))
 				{
 				{
-					String str = scope String();
-					str.Append(insertText, 0, insertText.Length - 2);
-                    mTargetEditWidget.Content.InsertAtCursor(str, .NoRestoreSelectionOnUndo);
+				    if (keyChar == '\t')
+				        sewc.InsertCharPair(insertText);
+				    else if (keyChar == '<')
+					{
+						var str = scope String();
+						str.Append(insertText, 0, insertText.Length - 2);
+				        sewc.InsertAtCursor(str, .NoRestoreSelectionOnUndo);
+					}
+				    else
+						sewc.InsertAtCursor(insertText, .NoRestoreSelectionOnUndo);
 				}
 				}
-                else
-                    mTargetEditWidget.Content.InsertAtCursor(insertText, .NoRestoreSelectionOnUndo);
-            }
-            else
-                mTargetEditWidget.Content.InsertAtCursor(insertText, .NoRestoreSelectionOnUndo);
-
-            /*if (mIsAsync)
-                UpdateAsyncInfo();*/
+				else
+					sewc.InsertAtCursor(insertText, .NoRestoreSelectionOnUndo);
 
 
-			if (implText != null)
-				InsertImplText(sourceEditWidgetContent, implText);
+				if (implText != null)
+					InsertImplText(sewc, implText);
+			}
 
 
-            if (persistentInvokeSrcPositons != null)
-            {
-                for (int32 i = 0; i < mInvokeSrcPositions.Count; i++)
-                {
-					//TEST
-                    //var persistentTextPositon = persistentInvokeSrcPositons[i + 100];
+			// Load persistent text positions back
+			if (persistentInvokeSrcPositons != null)
+			{
+				for (int32 i = 0; i < mInvokeSrcPositions.Count; i++)
+				{
 					var persistentTextPositon = persistentInvokeSrcPositons[i];
 					var persistentTextPositon = persistentInvokeSrcPositons[i];
-                    mInvokeSrcPositions[i] = persistentTextPositon.mIndex;
-                    sourceEditWidgetContent.PersistentTextPositions.Remove(persistentTextPositon);
+				    mInvokeSrcPositions[i] = persistentTextPositon.mIndex;
+				    sewc.PersistentTextPositions.Remove(persistentTextPositon);
 					delete persistentTextPositon;
 					delete persistentTextPositon;
-                }
-            }
+				}
+			}
+
+			sewc.SetPrimaryTextCursor();
+			sewc.EnsureCursorVisible();
+		}
 
 
-            mTargetEditWidget.Content.EnsureCursorVisible();
-            if ((insertType != null) && (insertText.Length > 0))
-                insertType.Append(entry.mEntryType);
-        }
-		
 		public void MarkDirty()
 		public void MarkDirty()
 		{
 		{
 			if (mInvokeWidget != null)
 			if (mInvokeWidget != null)

+ 109 - 0
IDE/src/ui/QuickFind.bf

@@ -225,6 +225,13 @@ namespace IDE.ui
 
 
             if (evt.mKeyCode == KeyCode.Return)
             if (evt.mKeyCode == KeyCode.Return)
             {
             {
+				var keyFlags = mWidgetWindow.GetKeyFlags(true);
+				if (keyFlags.HasFlag(.Alt))
+				{
+					SelectAllMatches(keyFlags.HasFlag(.Shift));
+					Close();
+					return;
+				}
                 if (evt.mSender == mFindEditWidget)
                 if (evt.mSender == mFindEditWidget)
                 {
                 {
                     FindNext(1, true);
                     FindNext(1, true);
@@ -249,6 +256,108 @@ namespace IDE.ui
                 DoReplace(true);            
                 DoReplace(true);            
         }
         }
 
 
+		bool SelectAllMatches(bool caseSensitive)
+		{
+			var ewc = mEditWidget.Content;
+			var sewc = ewc as SourceEditWidgetContent;
+			ewc.SetPrimaryTextCursor();
+
+			String findText = scope String();
+			mFindEditWidget.GetText(findText);
+			sLastSearchString.Set(findText);
+			if (findText.Length == 0)
+			    return false;
+
+			String findTextLower = scope String(findText);
+			findTextLower.ToLower();
+			String findTextUpper = scope String(findText);
+			findTextUpper.ToUpper();
+
+			int32 selStart = (mSelectionStart != null) ? mSelectionStart.mIndex : 0;
+			int32 selEnd = (mSelectionEnd != null) ? mSelectionEnd.mIndex : ewc.mData.mTextLength;
+
+
+			bool Matches(int32 idx)
+			{
+				if (idx + findText.Length >= ewc.mData.mTextLength)
+					return false;
+
+				for (var i = 0; i < findText.Length; i++)
+				{
+					var char = ewc.mData.mText[idx+i].mChar;
+
+					if (caseSensitive)
+					{
+						if (findText[i] != char)
+							return false;
+					}
+					else if ((findTextLower[i] != char) && (findTextUpper[i] != char))
+					{
+						return false;
+					}
+				}
+
+				return true;
+			}
+
+			var primaryCursor = ewc.mTextCursors.Front;
+			var initialCursorPos = primaryCursor.mCursorTextPos;
+			EditWidgetContent.TextCursor swapCursor = null;
+
+			ewc.RemoveSecondaryTextCursors();
+
+			primaryCursor.mJustInsertedCharPair = false;
+			primaryCursor.mCursorImplicitlyMoved = false;
+			primaryCursor.mVirtualCursorPos = null;
+
+			var isFirstMatch = true;
+
+			for (var idx = selStart; idx < selEnd; idx++)
+			{
+				if (!Matches(idx))
+					continue;
+
+				if (!isFirstMatch)
+				{
+					var cursor = ewc.mTextCursors.Add(.. new EditWidgetContent.TextCursor(-1, primaryCursor));
+					if (cursor.mCursorTextPos == initialCursorPos)
+						swapCursor = cursor;
+				}
+
+				isFirstMatch = false;
+
+				primaryCursor.mCursorTextPos = (int32)(idx + findText.Length);
+				primaryCursor.mSelection = EditSelection(idx, primaryCursor.mCursorTextPos);
+
+				idx += (int32)findText.Length;
+			}
+
+			// Remove selection when at least one match has been found
+			if ((sewc != null) && (!isFirstMatch))
+			{
+				if (mSelectionStart != null)
+				{
+					sewc.PersistentTextPositions.Remove(mSelectionStart);
+					DeleteAndNullify!(mSelectionStart);
+				}
+
+				if (mSelectionEnd != null)
+				{
+					sewc.PersistentTextPositions.Remove(mSelectionEnd);
+					DeleteAndNullify!(mSelectionEnd);
+				}
+			}
+
+			// Making sure that primary cursor is at the position where QuickFind found first match.
+			if (swapCursor != null)
+			{
+				Swap!(primaryCursor.mCursorTextPos, swapCursor.mCursorTextPos);
+				Swap!(primaryCursor.mSelection.Value, swapCursor.mSelection.Value);
+			}
+
+			return (!isFirstMatch);
+		}
+
         void EditWidgetSubmit(EditEvent editEvent)
         void EditWidgetSubmit(EditEvent editEvent)
         {            
         {            
             //FindNext(true);
             //FindNext(true);

+ 384 - 294
IDE/src/ui/SourceEditWidgetContent.bf

@@ -776,6 +776,7 @@ namespace IDE.ui
         public SourceViewPanel mSourceViewPanel;
         public SourceViewPanel mSourceViewPanel;
         //public bool mAsyncAutocomplete;
         //public bool mAsyncAutocomplete;
         public bool mIsInKeyChar;
         public bool mIsInKeyChar;
+		public bool mDidAutoComplete;
         public bool mDbgDoTest;
         public bool mDbgDoTest;
         public int32 mCursorStillTicks;
         public int32 mCursorStillTicks;
         public static bool sReadOnlyErrorShown;
         public static bool sReadOnlyErrorShown;
@@ -1893,6 +1894,7 @@ namespace IDE.ui
 			bool startsWithNewline = (forceMatchIndent) && (str.StartsWith("\n"));
 			bool startsWithNewline = (forceMatchIndent) && (str.StartsWith("\n"));
 			bool isMultiline = str.Contains("\n");
 			bool isMultiline = str.Contains("\n");
 
 
+			CreateMultiCursorUndoBatch("SEWC.PasteText(str, forceMatchIndent)");
 			if (startsWithNewline || isMultiline)
 			if (startsWithNewline || isMultiline)
 			{
 			{
 				var undoBatchStart = new UndoBatchStart("pasteText");
 				var undoBatchStart = new UndoBatchStart("pasteText");
@@ -2222,6 +2224,7 @@ namespace IDE.ui
 
 
 		public void ScopePrev()
 		public void ScopePrev()
 		{
 		{
+			RemoveSecondaryTextCursors();
 			int pos = CursorTextPos - 1;
 			int pos = CursorTextPos - 1;
 			int openCount = 0;
 			int openCount = 0;
 
 
@@ -2254,6 +2257,7 @@ namespace IDE.ui
 
 
 		public void ScopeNext()
 		public void ScopeNext()
 		{
 		{
+			RemoveSecondaryTextCursors();
 			int pos = CursorTextPos;
 			int pos = CursorTextPos;
 			int openCount = 0;
 			int openCount = 0;
 
 
@@ -2297,6 +2301,7 @@ namespace IDE.ui
 
 
         public bool OpenCodeBlock()
         public bool OpenCodeBlock()
         {
         {
+			CreateMultiCursorUndoBatch("SEWC.OpenCodeBlock()");
             int lineIdx;
             int lineIdx;
 
 
             if (HasSelection())
             if (HasSelection())
@@ -2843,78 +2848,73 @@ namespace IDE.ui
 
 
 		public bool CommentBlock()
 		public bool CommentBlock()
 		{
 		{
-			bool? doComment = true;
-
 			if (CheckReadOnly())
 			if (CheckReadOnly())
 				return false;
 				return false;
 
 
-			var startLineAndCol = CursorLineAndColumn;
-			int startTextPos = CursorTextPos;
-			var prevSelection = mSelection;
-			bool hadSelection = HasSelection();
-
-			if ((!HasSelection()) && (doComment != null))
+			for (var cursor in mTextCursors)
 			{
 			{
-				CursorToLineEnd();
-				int cursorEndPos = CursorTextPos;
-				CursorToLineStart(false);
-				mSelection = .(CursorTextPos, cursorEndPos);
-			}
+				SetTextCursor(cursor);
 
 
-			if ((HasSelection()) && (mSelection.Value.Length > 1))
-			{
-				UndoBatchStart undoBatchStart = new UndoBatchStart("embeddedCommentBlock");
-				mData.mUndoManager.Add(undoBatchStart);
+				var startLineAndCol = CursorLineAndColumn;
+				int startTextPos = CursorTextPos;
+				var prevSelection = mSelection;
+				bool hadSelection = HasSelection();
 
 
-				var setCursorAction = new SetCursorAction(this);
-				setCursorAction.mSelection = prevSelection;
-				setCursorAction.mCursorTextPos = (.)startTextPos;
-				mData.mUndoManager.Add(setCursorAction);
+				if (!HasSelection())
+				{
+					CursorToLineEnd();
+					int cursorEndPos = CursorTextPos;
+					CursorToLineStart(false);
+					mSelection = EditSelection(CursorTextPos, cursorEndPos);
+				}
 
 
-				int minPos = mSelection.GetValueOrDefault().MinPos;
-				int maxPos = mSelection.GetValueOrDefault().MaxPos;
-				mSelection = null;
+				if (HasSelection())
+				{
+					CreateMultiCursorUndoBatch("SEWC.CommentBlock()");
+					var setCursorAction = new SetCursorAction(this);
+					setCursorAction.mSelection = prevSelection;
+					setCursorAction.mCursorTextPos = (int32)startTextPos;
+					mData.mUndoManager.Add(setCursorAction);
 
 
-				var str = scope String();
-				ExtractString(minPos, maxPos - minPos, str);
-				
-				var trimmedStr = scope String();
-				trimmedStr.Append(str);
-				int32 startLen = (int32)trimmedStr.Length;
-				trimmedStr.TrimStart();
-				int32 afterTrimStart = (int32)trimmedStr.Length;
-				trimmedStr.TrimEnd();
-				int32 afterTrimEnd = (int32)trimmedStr.Length;
+					int minPos = mSelection.GetValueOrDefault().MinPos;
+					int maxPos = mSelection.GetValueOrDefault().MaxPos;
+					mSelection = null;
+
+					var str = scope String();
+					ExtractString(minPos, maxPos - minPos, str);
+					
+					var trimmedStr = scope String();
+					trimmedStr.Append(str);
+					int32 startLen = (int32)trimmedStr.Length;
+					trimmedStr.TrimStart();
+					int32 afterTrimStart = (int32)trimmedStr.Length;
+					trimmedStr.TrimEnd();
+					int32 afterTrimEnd = (int32)trimmedStr.Length;
 
 
-				int firstCharPos = minPos + (startLen - afterTrimStart);
-				int lastCharPos = maxPos - (afterTrimStart - afterTrimEnd);
+					int firstCharPos = minPos + (startLen - afterTrimStart);
+					int lastCharPos = maxPos - (afterTrimStart - afterTrimEnd);
 
 
-				if (doComment != false)
-				{
 					CursorTextPos = firstCharPos;
 					CursorTextPos = firstCharPos;
 					InsertAtCursor("/*");
 					InsertAtCursor("/*");
 					CursorTextPos = lastCharPos + 2;
 					CursorTextPos = lastCharPos + 2;
 					InsertAtCursor("*/");
 					InsertAtCursor("*/");
 
 
-					if (doComment != null)
-						mSelection = EditSelection(firstCharPos, lastCharPos + 4);
-				}
-
-				if (undoBatchStart != null)
-					mData.mUndoManager.Add(undoBatchStart.mBatchEnd);
-
-				if (startTextPos <= minPos)
-					CursorLineAndColumn = startLineAndCol;
-				else if (startTextPos < maxPos)
-					CursorTextPos = startTextPos + 2;
+					mSelection = EditSelection(firstCharPos, lastCharPos + 4);
 
 
-				if ((doComment == null) || (!hadSelection))
-					mSelection = null;
+					if (startTextPos <= minPos)
+						CursorLineAndColumn = startLineAndCol;
+					else if (startTextPos < maxPos)
+						CursorTextPos = startTextPos + 2;
 
 
-			    return true;
+					if (!hadSelection)
+						mSelection = null;
+				}
 			}
 			}
 
 
-			return false;
+			CloseMultiCursorUndoBatch();
+			SetPrimaryTextCursor();
+
+			return true;
 		}
 		}
 
 
 		public bool CommentLines()
 		public bool CommentLines()
@@ -2922,157 +2922,167 @@ namespace IDE.ui
 			if (CheckReadOnly())
 			if (CheckReadOnly())
 				return false;
 				return false;
 
 
-			int startTextPos = CursorTextPos;
-			var prevSelection = mSelection;
-			bool hadSelection = HasSelection();
-			var startLineAndCol = CursorLineAndColumn;
-			if (!HasSelection())
-			{
-				CursorToLineEnd();
-				int cursorEndPos = CursorTextPos;
-				CursorToLineStart(false);
-				mSelection = .(CursorTextPos, cursorEndPos);
-			}
+			var sortedCursors = mTextCursors;
+			if (mTextCursors.Count > 1)
+				sortedCursors = GetSortedCursors(.. scope :: List<TextCursor>());
 
 
-			UndoBatchStart undoBatchStart = new UndoBatchStart("embeddedCommentLines");
-			mData.mUndoManager.Add(undoBatchStart);
+			// Forcing creation of undo batch, because even single cursor has
+			// multiple undo-actions.
+			CreateMultiCursorUndoBatch("SEWC.CommentLines()", force: true);
 
 
-			var setCursorAction = new SetCursorAction(this);
-			setCursorAction.mSelection = prevSelection;
-			setCursorAction.mCursorTextPos = (.)startTextPos;
-			mData.mUndoManager.Add(setCursorAction);
+			for (var cursor in sortedCursors)
+			{
+				SetTextCursor(cursor);
 
 
-			int minPos = mSelection.GetValueOrDefault().MinPos;
-			int maxPos = mSelection.GetValueOrDefault().MaxPos;
-			mSelection = null;
+				var startTextPos = CursorTextPos;
+				var prevSelection = mSelection;
+				var hadSelection = HasSelection();
+				var startLineAndCol = CursorLineAndColumn;
+				if (!HasSelection())
+				{
+					CursorToLineEnd();
+					int cursorEndPos = CursorTextPos;
+					CursorToLineStart(false);
+					mSelection = .(CursorTextPos, cursorEndPos);
+				}
 
 
-			while (minPos > 0)
-			{
-				var c = mData.mText[minPos - 1].mChar;
-				if (c == '\n')
-					break;
-				minPos--;
-			}
+				var setCursorAction = new SetCursorAction(this);
+				setCursorAction.mSelection = prevSelection;
+				setCursorAction.mCursorTextPos = (int32)startTextPos;
+				mData.mUndoManager.Add(setCursorAction);
 
 
-			bool hadMaxChar = false;
-			int checkMaxPos = maxPos;
-			while (checkMaxPos > 0)
-			{
-				var c = mData.mText[checkMaxPos - 1].mChar;
-				if (c == '\n')
-					break;
-				if ((c != '\t') && (c != ' '))
+				int minPos = mSelection.Value.MinPos;
+				int maxPos = mSelection.Value.MaxPos;
+				mSelection = null;
+
+				while (minPos > 0)
 				{
 				{
-					hadMaxChar = true;
-					break;
+					var c = mData.mText[minPos - 1].mChar;
+					if (c == '\n')
+						break;
+					minPos--;
 				}
 				}
-				checkMaxPos--;
-			}
 
 
-			if (!hadMaxChar)
-			{
-				checkMaxPos = maxPos;
-				while (checkMaxPos < mData.mTextLength)
+				bool hadMaxChar = false;
+				int checkMaxPos = maxPos;
+				while (checkMaxPos > 0)
 				{
 				{
-					var c = mData.mText[checkMaxPos].mChar;
+					var c = mData.mText[checkMaxPos - 1].mChar;
 					if (c == '\n')
 					if (c == '\n')
 						break;
 						break;
 					if ((c != '\t') && (c != ' '))
 					if ((c != '\t') && (c != ' '))
 					{
 					{
-						maxPos = checkMaxPos + 1;
+						hadMaxChar = true;
 						break;
 						break;
 					}
 					}
-					checkMaxPos++;
+					checkMaxPos--;
 				}
 				}
-			}
-		
-			int wantLineCol = -1;
-			int lineStartCol = 0;
-			bool didLineComment = false;
 
 
-			for (int i = minPos; i < maxPos; i++)
-			{
-				var c = mData.mText[i].mChar;
-				if (didLineComment)
+				if (!hadMaxChar)
 				{
 				{
-					if (c == '\n')
+					checkMaxPos = maxPos;
+					while (checkMaxPos < mData.mTextLength)
 					{
 					{
-						didLineComment = false;
-						lineStartCol = 0;
+						var c = mData.mText[checkMaxPos].mChar;
+						if (c == '\n')
+							break;
+						if ((c != '\t') && (c != ' '))
+						{
+							maxPos = checkMaxPos + 1;
+							break;
+						}
+						checkMaxPos++;
 					}
 					}
-					continue;
 				}
 				}
-				if (c == '\t')
-					lineStartCol += gApp.mSettings.mEditorSettings.mTabSize;
-				else if (c == ' ')
-					lineStartCol++;
-				else
+
+				int wantLineCol = -1;
+				int lineStartCol = 0;
+				bool didLineComment = false;
+
+				for (int i = minPos; i < maxPos; i++)
 				{
 				{
-					if (wantLineCol == -1)
-						wantLineCol = lineStartCol;
+					var c = mData.mText[i].mChar;
+					if (didLineComment)
+					{
+						if (c == '\n')
+						{
+							didLineComment = false;
+							lineStartCol = 0;
+						}
+						continue;
+					}
+					if (c == '\t')
+						lineStartCol += gApp.mSettings.mEditorSettings.mTabSize;
+					else if (c == ' ')
+						lineStartCol++;
 					else
 					else
-						wantLineCol = Math.Min(wantLineCol, lineStartCol);
-					didLineComment = true;
+					{
+						if (wantLineCol == -1)
+							wantLineCol = lineStartCol;
+						else
+							wantLineCol = Math.Min(wantLineCol, lineStartCol);
+						didLineComment = true;
+					}
 				}
 				}
-			}
-			wantLineCol = Math.Max(0, wantLineCol);
+				wantLineCol = Math.Max(0, wantLineCol);
 
 
-			didLineComment = false;
-			lineStartCol = 0;
-			int appendedCount = 0;
-			for (int i = minPos; i < maxPos; i++)
-			{
-				var c = mData.mText[i].mChar;
-				if (didLineComment)
+				didLineComment = false;
+				lineStartCol = 0;
+				int appendedCount = 0;
+				for (int i = minPos; i < maxPos; i++)
 				{
 				{
-					if (c == '\n')
+					var c = mData.mText[i].mChar;
+					if (didLineComment)
 					{
 					{
-						didLineComment = false;
-						lineStartCol = 0;
+						if (c == '\n')
+						{
+							didLineComment = false;
+							lineStartCol = 0;
+						}
+						continue;
 					}
 					}
-					continue;
-				}
 
 
-				bool commentNow = false;
-				if ((wantLineCol != -1) && (lineStartCol >= wantLineCol))
-					commentNow = true;
+					bool commentNow = false;
+					if ((wantLineCol != -1) && (lineStartCol >= wantLineCol))
+						commentNow = true;
 
 
-				if (c == '\t')
-					lineStartCol += gApp.mSettings.mEditorSettings.mTabSize;
-				else if (c == ' ')
-					lineStartCol++;
-				else
-					commentNow = true;
+					if (c == '\t')
+						lineStartCol += gApp.mSettings.mEditorSettings.mTabSize;
+					else if (c == ' ')
+						lineStartCol++;
+					else
+						commentNow = true;
 
 
-				if (commentNow)
-				{
-					CursorTextPos = i;
-					String str = scope .();
-					while (lineStartCol + gApp.mSettings.mEditorSettings.mTabSize <= wantLineCol)
+					if (commentNow)
 					{
 					{
-						lineStartCol += gApp.mSettings.mEditorSettings.mTabSize;
-						str.Append("\t");
+						CursorTextPos = i;
+						String str = scope .();
+						while (lineStartCol + gApp.mSettings.mEditorSettings.mTabSize <= wantLineCol)
+						{
+							lineStartCol += gApp.mSettings.mEditorSettings.mTabSize;
+							str.Append("\t");
+						}
+						str.Append("//");
+						InsertAtCursor(str);
+						didLineComment = true;
+						maxPos += str.Length;
+						if (i <= startTextPos + appendedCount)
+							appendedCount += str.Length;
 					}
 					}
-					str.Append("//");
-					InsertAtCursor(str);
-					didLineComment = true;
-					maxPos += str.Length;
-					if (i <= startTextPos + appendedCount)
-						appendedCount += str.Length;
 				}
 				}
-			}
-			mSelection = EditSelection(minPos, maxPos);
+				mSelection = EditSelection(minPos, maxPos);
 
 
-			if (undoBatchStart != null)
-				mData.mUndoManager.Add(undoBatchStart.mBatchEnd);
+				if (appendedCount > 0)
+					CursorTextPos = startTextPos + appendedCount;
+				else
+					CursorLineAndColumn = startLineAndCol;
 
 
-			if (appendedCount > 0)
-				CursorTextPos = startTextPos + appendedCount;
-			else
-				CursorLineAndColumn = startLineAndCol;
+				if (!hadSelection)
+					mSelection = null;
+			}
 
 
-			if (!hadSelection)
-				mSelection = null;
+			CloseMultiCursorUndoBatch();
+			SetPrimaryTextCursor();
 
 
 			return true;
 			return true;
 		}
 		}
@@ -3095,144 +3105,168 @@ namespace IDE.ui
 			if (CheckReadOnly())
 			if (CheckReadOnly())
 				return false;
 				return false;
 
 
-			int startTextPos = CursorTextPos;
-			bool doLineComment = false;
-			var prevSelection = mSelection;
+			var sortedCursors = mTextCursors;
+			if (mTextCursors.Count > 1)
+				sortedCursors = GetSortedCursors(.. scope :: List<TextCursor>());
 
 
-			LineAndColumn? startLineAndCol = CursorLineAndColumn;
-			if (!HasSelection())
+			var didComment = false;
+			for (var cursor in sortedCursors)
 			{
 			{
-				CursorToLineEnd();
-				int cursorEndPos = CursorTextPos;
-				CursorToLineStart(false);
-				mSelection = .(CursorTextPos, cursorEndPos);
-				doLineComment = true;
-			}
+				SetTextCursor(cursor);
 
 
-			if ((HasSelection()) && (mSelection.Value.Length > 0))
-			{
-				UndoBatchStart undoBatchStart = new UndoBatchStart("embeddedToggleComment");
-				mData.mUndoManager.Add(undoBatchStart);
+				int startTextPos = CursorTextPos;
+				bool doLineComment = false;
+				var prevSelection = mSelection;
+				var cursorAtEndPos = true;
 
 
-				var setCursorAction = new SetCursorAction(this);
-				setCursorAction.mSelection = prevSelection;
-				setCursorAction.mCursorTextPos = (.)startTextPos;
-				mData.mUndoManager.Add(setCursorAction);
+				LineAndColumn? startLineAndCol = CursorLineAndColumn;
+				if (!HasSelection())
+				{
+					CursorToLineEnd();
+					int cursorEndPos = CursorTextPos;
+					CursorToLineStart(false);
+					mSelection = EditSelection(CursorTextPos, cursorEndPos);
+					doLineComment = true;
+				}
+				else
+				{
+					cursorAtEndPos = (mSelection.Value.mStartPos == mCursorTextPos);
+				}
 
 
-				int minPos = mSelection.GetValueOrDefault().MinPos;
-				int maxPos = mSelection.GetValueOrDefault().MaxPos;
-				mSelection = null;
+				if (HasSelection())
+				{
+					CreateMultiCursorUndoBatch("SEWC.ToggleComment()", force: true);
 
 
-				var str = scope String();
-				ExtractString(minPos, maxPos - minPos, str);
-				var trimmedStr = scope String();
-				trimmedStr.Append(str);
-				int32 startLen = (int32)trimmedStr.Length;
-				trimmedStr.TrimStart();
-				int32 afterTrimStart = (int32)trimmedStr.Length;
-				trimmedStr.TrimEnd();
-				int32 afterTrimEnd = (int32)trimmedStr.Length;
-				trimmedStr.Append('\n');
+					var setCursorAction = new SetCursorAction(this);
+					setCursorAction.mSelection = prevSelection;
+					setCursorAction.mCursorTextPos = (int32)startTextPos;
+					mData.mUndoManager.Add(setCursorAction);
 
 
-				int firstCharPos = minPos + (startLen - afterTrimStart);
-				int lastCharPos = maxPos - (afterTrimStart - afterTrimEnd);
+					var minPos = mSelection.GetValueOrDefault().MinPos;
+					var maxPos = mSelection.GetValueOrDefault().MaxPos;
+					mSelection = null;
 
 
-				if (afterTrimEnd == 0)
-				{
-					if (undoBatchStart != null)
-						mData.mUndoManager.Add(undoBatchStart.mBatchEnd);
+					var str = scope String();
+					ExtractString(minPos, (maxPos - minPos), str);
+					var trimmedStr = scope String();
+					trimmedStr.Append(str);
+					int32 startLen = (int32)trimmedStr.Length;
+					trimmedStr.TrimStart();
+					int32 afterTrimStart = (int32)trimmedStr.Length;
+					trimmedStr.TrimEnd();
+					int32 afterTrimEnd = (int32)trimmedStr.Length;
+					trimmedStr.Append('\n');
 
 
-					CursorLineAndColumn = startLineAndCol.Value;
+					int firstCharPos = minPos + (startLen - afterTrimStart);
+					int lastCharPos = maxPos - (afterTrimStart - afterTrimEnd);
 
 
-					if (doComment == null)
-						mSelection = null;
+					if (afterTrimEnd == 0)
+					{
+						CursorLineAndColumn = startLineAndCol.Value;
 
 
-					return false; // not sure if this should be false in blank/only whitespace selection case
-				}
-				else if ((doComment != true) && (trimmedStr.StartsWith("//"))) 
-				{
-					for (int i = firstCharPos; i <= lastCharPos; i++)
+						if (doComment == null)
+							mSelection = null;
+
+						//return false; // not sure if this should be false in blank/only whitespace selection case
+						continue;
+					}
+					else if ((doComment != true) && (trimmedStr.StartsWith("//")))
 					{
 					{
-						if (((minPos == 0) && (i == 0)) ||
-							((minPos >= 0) && (SafeGetChar(i - 1) == '\n') || (SafeGetChar(i - 1) == '\t') || (SafeGetChar(i - 1) == ' ')))
+						didComment = true;
+						for (int i = firstCharPos; i <= lastCharPos; i++)
 						{
 						{
-							if (SafeGetChar(i - 0) == '/' && SafeGetChar(i + 1) == '/')
+							if (((minPos == 0) && (i == 0)) ||
+								((minPos >= 0) && (SafeGetChar(i - 1) == '\n') || (SafeGetChar(i - 1) == '\t') || (SafeGetChar(i - 1) == ' ')))
 							{
 							{
-								mSelection = EditSelection(i - 0, i + 2);
-								DeleteSelection();
-								lastCharPos -= 2;
-								while (i < maxPos && SafeGetChar(i) != '\n')
+								if (SafeGetChar(i - 0) == '/' && SafeGetChar(i + 1) == '/')
 								{
 								{
-									i++;
+									mSelection = EditSelection(i - 0, i + 2);
+									DeleteSelection();
+									lastCharPos -= 2;
+									while (i < maxPos && SafeGetChar(i) != '\n')
+									{
+										i++;
+									}
 								}
 								}
 							}
 							}
 						}
 						}
-					}
 
 
-					startLineAndCol = null;
-					CursorToLineEnd();
-					int cursorEndPos = CursorTextPos;
-					mSelection = .(minPos, cursorEndPos);
-				}
-				else if ((doComment != true) && (trimmedStr.StartsWith("/*")))
-				{
-					if (trimmedStr.EndsWith("*/\n"))
+						startLineAndCol = null;
+						CursorToLineEnd();
+						int cursorEndPos = CursorTextPos;
+						mSelection = .(minPos, cursorEndPos);
+					}
+					else if ((doComment != true) && trimmedStr.StartsWith("/*"))
 					{
 					{
-						mSelection = EditSelection(firstCharPos, firstCharPos + 2);
-						DeleteChar();
-						mSelection = EditSelection(lastCharPos - 4, lastCharPos - 2);
-						DeleteChar();
+						didComment = true;
+						if (trimmedStr.EndsWith("*/\n"))
+						{
+							mSelection = EditSelection(firstCharPos, firstCharPos + 2);
+							DeleteChar();
+							mSelection = EditSelection(lastCharPos - 4, lastCharPos - 2);
+							DeleteChar();
 
 
-						if (prevSelection != null)
-							mSelection = EditSelection(firstCharPos, lastCharPos - 4);
+							if (prevSelection != null)
+								mSelection = EditSelection(firstCharPos, lastCharPos - 4);
+						}
 					}
 					}
-				}
-				else if (doComment != false)
-				{
-					//if selection is from beginning of the line then we want to use // comment, that's why the check for line count and ' ' and tab
-					if (doLineComment)
+					else if (doComment != false)
 					{
 					{
-						CursorTextPos = minPos;
-						InsertAtCursor("//"); //goes here if no selection
+						didComment = true;
+						//if selection is from beginning of the line then we want to use // comment, that's why the check for line count and ' ' and tab
+						if (doLineComment)
+						{
+							CursorTextPos = minPos;
+							InsertAtCursor("//"); //goes here if no selection
+						}
+						else
+						{
+							CursorTextPos = firstCharPos;
+							InsertAtCursor("/*");
+							CursorTextPos = lastCharPos + 2;
+							InsertAtCursor("*/");
+						}
+
+						mSelection = EditSelection(firstCharPos, lastCharPos + 4);
+						if (startTextPos <= minPos)
+							CursorLineAndColumn = startLineAndCol.Value;
+						else
+							CursorTextPos = startTextPos + 2;
+						startLineAndCol = null;
 					}
 					}
 					else
 					else
 					{
 					{
-						CursorTextPos = firstCharPos;
-						InsertAtCursor("/*");
-						CursorTextPos = lastCharPos + 2;
-						InsertAtCursor("*/");
+						mSelection = prevSelection;
 					}
 					}
 
 
-					mSelection = EditSelection(firstCharPos, lastCharPos + 4);
-					if (startTextPos <= minPos)
+					if (startLineAndCol != null)
 						CursorLineAndColumn = startLineAndCol.Value;
 						CursorLineAndColumn = startLineAndCol.Value;
-					else
-						CursorTextPos = startTextPos + 2;
-					startLineAndCol = null;
-				}
-				else
-				{
-					mSelection = prevSelection;
-				}
-
-				if (undoBatchStart != null)
-					mData.mUndoManager.Add(undoBatchStart.mBatchEnd);
 
 
-				if (startLineAndCol != null)
-					CursorLineAndColumn = startLineAndCol.Value;
+					if (prevSelection == null)
+						mSelection = null;
 
 
-				if (prevSelection == null)
-					mSelection = null;
+					ClampCursor();
+					FixSelection();
 
 
-				ClampCursor();
-				FixSelection();
+					if (mSelection.HasValue)
+					{
+						// Placing cursor where it was before, meaning
+						// at the start or at the end of the selection.
+						mCursorTextPos = (cursorAtEndPos)
+							? (int32)mSelection.Value.mStartPos
+							: (int32)mSelection.Value.mEndPos
+							;
+					}
+				}
 
 
-				return true;
 			}
 			}
 
 
-			return false;
+			CloseMultiCursorUndoBatch();
+			SetPrimaryTextCursor();
+
+			return (didComment);
 		}
 		}
-		
+
 		public void DeleteAllRight()
 		public void DeleteAllRight()
 		{
 		{
 			int startPos;
 			int startPos;
@@ -3273,22 +3307,45 @@ namespace IDE.ui
 			if ((CheckReadOnly()) || (!mAllowVirtualCursor))
 			if ((CheckReadOnly()) || (!mAllowVirtualCursor))
 				return;
 				return;
 
 
-			UndoBatchStart undoBatchStart = new UndoBatchStart("duplicateLine");
-			mData.mUndoManager.Add(undoBatchStart);
+			var lineText = scope String();
+			var sortedCursors = mTextCursors;
+			if (mTextCursors.Count > 1)
+				sortedCursors = GetSortedCursors(.. scope :: List<TextCursor>());
 
 
-			mData.mUndoManager.Add(new SetCursorAction(this));
+			// Forcing creation of undo batch, because even single cursor has
+			// multiple undo-actions (SetCursorAction + InsertTextAction).
+			CreateMultiCursorUndoBatch("SEWC.DuplicateLine()", force: true);
 
 
-			var prevCursorLineAndColumn = CursorLineAndColumn;
-			int lineNum = CursorLineAndColumn.mLine;
-			GetLinePosition(lineNum, var lineStart, var lineEnd);
-			var str = scope String();
-			GetLineText(lineNum, str);
-			mSelection = null;
-			CursorLineAndColumn = LineAndColumn(lineNum, 0);
-			PasteText(str, "line");
-			CursorLineAndColumn = LineAndColumn(prevCursorLineAndColumn.mLine + 1, prevCursorLineAndColumn.mColumn);
+			for (var cursor in sortedCursors.Reversed)
+			{
+				SetTextCursor(cursor);
+				mData.mUndoManager.Add(new SetCursorAction(this));
 
 
-			mData.mUndoManager.Add(undoBatchStart.mBatchEnd);
+				var line = CursorLineAndColumn.mLine;
+				var column = CursorLineAndColumn.mColumn;
+				var prevCursorPos = mCursorTextPos;
+
+				lineText.Clear();
+				GetLineText(line, lineText);
+
+				mSelection = null;
+				CursorLineAndColumn = LineAndColumn(line+1, 0);
+
+				InsertAtCursor("\n");
+				CursorLineAndColumn = LineAndColumn(line+1, 0);
+
+				InsertAtCursor(lineText);
+				CursorLineAndColumn = LineAndColumn(line+1, column);
+
+				// Forcing calculation of CursorTextPos,
+				// if this cursor had one before.
+				if (prevCursorPos != -1)
+					var _ = CursorTextPos;
+			}
+
+			CloseMultiCursorUndoBatch();
+			SetPrimaryTextCursor();
+			EnsureCursorVisible();
 		}
 		}
 
 
 		enum StatementRangeFlags
 		enum StatementRangeFlags
@@ -3529,6 +3586,7 @@ namespace IDE.ui
 
 
 		void MoveSelection(int toLinePos, bool isStatementAware)
 		void MoveSelection(int toLinePos, bool isStatementAware)
 		{
 		{
+			RemoveSecondaryTextCursors();
 			/*if (GetStatementRange(CursorTextPos, var startIdx, var endIdx))
 			/*if (GetStatementRange(CursorTextPos, var startIdx, var endIdx))
 			{
 			{
 				mSelection = .(startIdx, endIdx);
 				mSelection = .(startIdx, endIdx);
@@ -3685,6 +3743,7 @@ namespace IDE.ui
 
 
 		public void MoveLine(VertDir dir)
 		public void MoveLine(VertDir dir)
 		{
 		{
+			RemoveSecondaryTextCursors();
 			int lineNum = CursorLineAndColumn.mLine;
 			int lineNum = CursorLineAndColumn.mLine;
 
 
 			if ((dir == .Up && lineNum < 1) || (dir == .Down && lineNum >= GetLineCount() - 1))
 			if ((dir == .Up && lineNum < 1) || (dir == .Down && lineNum >= GetLineCount() - 1))
@@ -3713,6 +3772,7 @@ namespace IDE.ui
 
 
 		public void MoveStatement(VertDir dir)
 		public void MoveStatement(VertDir dir)
 		{
 		{
+			RemoveSecondaryTextCursors();
 			int lineNum = CursorLineAndColumn.mLine;
 			int lineNum = CursorLineAndColumn.mLine;
 			int origLineNum = lineNum;
 			int origLineNum = lineNum;
 			GetLinePosition(lineNum, var lineStart, var lineEnd);
 			GetLinePosition(lineNum, var lineStart, var lineEnd);
@@ -3795,6 +3855,7 @@ namespace IDE.ui
 
 
 		void InsertCharPair(String charPair)
 		void InsertCharPair(String charPair)
 		{
 		{
+			CreateMultiCursorUndoBatch("SEWC.InsertCharPair()");
 			base.InsertCharPair(charPair);
 			base.InsertCharPair(charPair);
 			mCurParenPairIdSet.Add(mData.mNextCharId - 2);
 			mCurParenPairIdSet.Add(mData.mNextCharId - 2);
 		}
 		}
@@ -3803,12 +3864,18 @@ namespace IDE.ui
         {
         {
 			scope AutoBeefPerf("SEWC.KeyChar");
 			scope AutoBeefPerf("SEWC.KeyChar");
 
 
+			if (IsPrimaryTextCursor())
+				mDidAutoComplete = false;
+
 			var keyChar;
 			var keyChar;
 			
 			
 
 
 			if (mIgnoreKeyChar)
 			if (mIgnoreKeyChar)
 			{
 			{
-				mIgnoreKeyChar = false;
+				// Only flip the flag when we are processing last cursor
+				if (mTextCursors.Back.mId == mCurrentTextCursor.mId)
+					mIgnoreKeyChar = false;
+
 				return;
 				return;
 			}
 			}
 
 
@@ -3825,6 +3892,10 @@ namespace IDE.ui
 				 ((keyChar == '\r') && (autoCompleteOnEnter))) &&
 				 ((keyChar == '\r') && (autoCompleteOnEnter))) &&
 				(!mWidgetWindow.IsKeyDown(.Shift));
 				(!mWidgetWindow.IsKeyDown(.Shift));
 
 
+			// Skip completion-char for secondary cursors when AutoComplete just happened
+			if ((isCompletionChar) && (!IsPrimaryTextCursor()) && (mDidAutoComplete))
+				return;
+
             if ((gApp.mSymbolReferenceHelper != null) && (gApp.mSymbolReferenceHelper.IsRenaming))
             if ((gApp.mSymbolReferenceHelper != null) && (gApp.mSymbolReferenceHelper.IsRenaming))
             {         
             {         
                 if ((keyChar == '\r') || (keyChar == '\n'))
                 if ((keyChar == '\r') || (keyChar == '\n'))
@@ -3883,7 +3954,7 @@ namespace IDE.ui
 			}
 			}
 
 
 			bool forceAutoCompleteInsert = false;
 			bool forceAutoCompleteInsert = false;
-			if (isEndingChar)
+			if ((IsPrimaryTextCursor()) && (isEndingChar))
             {
             {
 				bool forceAsyncFinish = false;
 				bool forceAsyncFinish = false;
 				if (mCursorTextPos > 0)
 				if (mCursorTextPos > 0)
@@ -3927,11 +3998,11 @@ namespace IDE.ui
             }
             }
 			else
 			else
 			{
 			{
-				if ((doAutocomplete) && (mOnFinishAsyncAutocomplete != null))
+				if ((IsPrimaryTextCursor()) && (doAutocomplete) && (mOnFinishAsyncAutocomplete != null))
 					mOnFinishAsyncAutocomplete();
 					mOnFinishAsyncAutocomplete();
 			}
 			}
 
 
-            if ((mAutoComplete != null) && (mAutoComplete.mAutoCompleteListWidget != null))
+            if ((IsPrimaryTextCursor()) && (mAutoComplete != null) && (mAutoComplete.mAutoCompleteListWidget != null))
             {
             {
 				if ((mAutoComplete.mInsertEndIdx != -1) && (mAutoComplete.mInsertEndIdx != mCursorTextPos) && (keyChar != '\t') && (keyChar != '\r') && (keyChar != '\n'))
 				if ((mAutoComplete.mInsertEndIdx != -1) && (mAutoComplete.mInsertEndIdx != mCursorTextPos) && (keyChar != '\t') && (keyChar != '\r') && (keyChar != '\n'))
 					doAutocomplete = false;
 					doAutocomplete = false;
@@ -3965,6 +4036,8 @@ namespace IDE.ui
 					if (mOnFinishAsyncAutocomplete != null)
 					if (mOnFinishAsyncAutocomplete != null)
 						mOnFinishAsyncAutocomplete();
 						mOnFinishAsyncAutocomplete();
 
 
+					mDidAutoComplete = true;
+					CreateMultiCursorUndoBatch("SEWC.KeyChar(autocomplete)");
                     UndoBatchStart undoBatchStart = new UndoBatchStart("autocomplete");
                     UndoBatchStart undoBatchStart = new UndoBatchStart("autocomplete");
                     mData.mUndoManager.Add(undoBatchStart);
                     mData.mUndoManager.Add(undoBatchStart);
 
 
@@ -4033,6 +4106,7 @@ namespace IDE.ui
 
 
             if (((keyChar == '\n') || (keyChar == '\r')) && (!HasSelection()) && (mIsMultiline) && (!CheckReadOnly()))
             if (((keyChar == '\n') || (keyChar == '\r')) && (!HasSelection()) && (mIsMultiline) && (!CheckReadOnly()))
             {
             {
+				CreateMultiCursorUndoBatch("SEWC.KeyChar(\n||\r)");
                 UndoBatchStart undoBatchStart = new UndoBatchStart("newline");
                 UndoBatchStart undoBatchStart = new UndoBatchStart("newline");
                 mData.mUndoManager.Add(undoBatchStart);                
                 mData.mUndoManager.Add(undoBatchStart);                
 
 
@@ -4133,7 +4207,7 @@ namespace IDE.ui
 					}
 					}
                 }
                 }
                 
                 
-                if ((mAutoComplete != null) && (mAutoComplete.mInvokeWidget != null))
+                if ((IsPrimaryTextCursor()) && (mAutoComplete != null) && (mAutoComplete.mInvokeWidget != null))
                 {
                 {
                     // Update the position of the invoke widget
                     // Update the position of the invoke widget
 					if (IsCursorVisible(false))
 					if (IsCursorVisible(false))
@@ -4149,11 +4223,13 @@ namespace IDE.ui
 				}
 				}
 				else
 				else
 				{
 				{
-					if (mAutoComplete != null)
+					if (IsPrimaryTextCursor() && mAutoComplete != null)
 						mAutoComplete.CloseListWindow();
 						mAutoComplete.CloseListWindow();
+						//shouldCloseAutoComplete = true;
 				}
 				}
-				
-				mAutoComplete?.UpdateAsyncInfo();
+
+				if (IsPrimaryTextCursor())
+					mAutoComplete?.UpdateAsyncInfo();
 
 
                 return;
                 return;
             }
             }
@@ -4176,6 +4252,7 @@ namespace IDE.ui
 							(mData.mText[cursorTextPos - 1].mChar == '*') &&
 							(mData.mText[cursorTextPos - 1].mChar == '*') &&
 							(mData.mText[cursorTextPos].mChar == '\n'))
 							(mData.mText[cursorTextPos].mChar == '\n'))
 						{
 						{
+							CreateMultiCursorUndoBatch("SEWC.KeyChar(comment)");
 							InsertAtCursor("*");
 							InsertAtCursor("*");
 							let prevLineAndColumn = mEditWidget.mEditWidgetContent.CursorLineAndColumn;
 							let prevLineAndColumn = mEditWidget.mEditWidgetContent.CursorLineAndColumn;
 							int column = GetLineEndColumn(prevLineAndColumn.mLine, false, true, true);
 							int column = GetLineEndColumn(prevLineAndColumn.mLine, false, true, true);
@@ -4290,6 +4367,7 @@ namespace IDE.ui
 					}
 					}
 					else if ((keyChar == '{') || (keyChar == '('))
 					else if ((keyChar == '{') || (keyChar == '('))
 					{
 					{
+						CreateMultiCursorUndoBatch("SEWC.KeyChar(blockSurround)");
 						UndoBatchStart undoBatchStart = new UndoBatchStart("blockSurround");
 						UndoBatchStart undoBatchStart = new UndoBatchStart("blockSurround");
 						mData.mUndoManager.Add(undoBatchStart);                
 						mData.mUndoManager.Add(undoBatchStart);                
 
 
@@ -4385,7 +4463,7 @@ namespace IDE.ui
                 mIsInKeyChar = false;
                 mIsInKeyChar = false;
             }
             }
 
 
-            if ((keyChar == '\b') || (keyChar == '\r') || (keyChar >= (char8)32))
+            if (IsPrimaryTextCursor() && ((keyChar == '\b') || (keyChar == '\r') || (keyChar >= (char8)32)))
             {
             {
                 bool isHighPri = (keyChar == '(') || (keyChar == '.');
                 bool isHighPri = (keyChar == '(') || (keyChar == '.');
 				bool needsFreshAutoComplete = ((isHighPri) /*|| (!mAsyncAutocomplete)*/ || (mAutoComplete == null) || (mAutoComplete.mAutoCompleteListWidget == null));
 				bool needsFreshAutoComplete = ((isHighPri) /*|| (!mAsyncAutocomplete)*/ || (mAutoComplete == null) || (mAutoComplete.mAutoCompleteListWidget == null));
@@ -4414,7 +4492,7 @@ namespace IDE.ui
             }
             }
             else if (mData.mCurTextVersionId != startRevision)
             else if (mData.mCurTextVersionId != startRevision)
             {
             {
-                if (mAutoComplete != null)
+                if (IsPrimaryTextCursor() && mAutoComplete != null)
                     mAutoComplete.CloseListWindow();
                     mAutoComplete.CloseListWindow();
             }
             }
 
 
@@ -4487,6 +4565,7 @@ namespace IDE.ui
 	                                mSelection.ValueRef.mStartPos = mSelection.Value.mEndPos - 1;
 	                                mSelection.ValueRef.mStartPos = mSelection.Value.mEndPos - 1;
 	                            if (mSelection.Value.mStartPos > 0)
 	                            if (mSelection.Value.mStartPos > 0)
 	                            {
 	                            {
+									CreateMultiCursorUndoBatch("SEWC.KeyChar(case)");
 	                                DeleteSelection();
 	                                DeleteSelection();
 	                                CursorToLineEnd();
 	                                CursorToLineEnd();
 	                            }
 	                            }
@@ -4509,6 +4588,7 @@ namespace IDE.ui
                         int32 columnPos = (int32)(GetTabbedWidth(tabStartStr, 0) / mCharWidth + 0.001f);
                         int32 columnPos = (int32)(GetTabbedWidth(tabStartStr, 0) / mCharWidth + 0.001f);
                         if (wantLineColumn > columnPos)
                         if (wantLineColumn > columnPos)
                         {
                         {
+							CreateMultiCursorUndoBatch("SEWC.KeyChar(else)");
                             String insertStr = scope String(' ', wantLineColumn - columnPos);
                             String insertStr = scope String(' ', wantLineColumn - columnPos);
 							insertStr.Append("else");
 							insertStr.Append("else");
 
 
@@ -4612,7 +4692,7 @@ namespace IDE.ui
 
 
             if (((keyCode == KeyCode.Up) || (keyCode == KeyCode.Down) || (keyCode == KeyCode.PageUp) || (keyCode == KeyCode.PageDown)))
             if (((keyCode == KeyCode.Up) || (keyCode == KeyCode.Down) || (keyCode == KeyCode.PageUp) || (keyCode == KeyCode.PageDown)))
             {
             {
-				if ((!autoCompleteRequireControl) || (mWidgetWindow.IsKeyDown(KeyCode.Control)))
+				if ((IsPrimaryTextCursor()) && ((!autoCompleteRequireControl) || (mWidgetWindow.IsKeyDown(KeyCode.Control))))
 				{
 				{
 	                if ((mAutoComplete != null) && (mAutoComplete.IsShowing()))
 	                if ((mAutoComplete != null) && (mAutoComplete.IsShowing()))
 	                {
 	                {
@@ -4707,7 +4787,7 @@ namespace IDE.ui
             int prevTextLength = mData.mTextLength;
             int prevTextLength = mData.mTextLength;
             base.KeyDown(keyCode, isRepeat);
             base.KeyDown(keyCode, isRepeat);
 
 
-            if ((mAutoComplete != null) &&
+            if ((IsPrimaryTextCursor()) && (mAutoComplete != null) &&
 				(keyCode != .Control) &&
 				(keyCode != .Control) &&
 				(keyCode != .Shift))
 				(keyCode != .Shift))
             {
             {
@@ -5020,6 +5100,7 @@ namespace IDE.ui
 						menuItem = menu.AddItem("Cut|Ctrl+X");
 						menuItem = menu.AddItem("Cut|Ctrl+X");
 						menuItem.mOnMenuItemSelected.Add(new (menu) =>
 						menuItem.mOnMenuItemSelected.Add(new (menu) =>
 							{
 							{
+								SetPrimaryTextCursor();
 								CutText();
 								CutText();
 							});
 							});
 						menuItem.SetDisabled(!hasSelection);
 						menuItem.SetDisabled(!hasSelection);
@@ -5034,6 +5115,7 @@ namespace IDE.ui
 						menuItem = menu.AddItem("Paste|Ctrl+V");
 						menuItem = menu.AddItem("Paste|Ctrl+V");
 						menuItem.mOnMenuItemSelected.Add(new (menu) =>
 						menuItem.mOnMenuItemSelected.Add(new (menu) =>
 							{
 							{
+								SetPrimaryTextCursor();
 								PasteText();
 								PasteText();
 							});
 							});
 
 
@@ -7240,5 +7322,13 @@ namespace IDE.ui
 
 
 			RehupLineCoords(animIdx, animLines);
 			RehupLineCoords(animIdx, animLines);
 		}
 		}
+
+		public override void RemoveSecondaryTextCursors(bool force = true)
+		{
+			if ((!force) && (mAutoComplete != null))
+				return;
+
+			base.RemoveSecondaryTextCursors(force);
+		}
     }
     }
 }
 }

+ 6 - 3
IDE/src/ui/SourceViewPanel.bf

@@ -1323,7 +1323,7 @@ namespace IDE.ui
 
 
 								embedSource.mTypeName = new .(useTypeName);
 								embedSource.mTypeName = new .(useTypeName);
 								if (embedHasFocus)
 								if (embedHasFocus)
-									embedSource.mCursorIdx = (.)embedSourceViewPanel.mEditWidget.mEditWidgetContent.CursorTextPos;
+									embedSource.mCursorIdx = (.)embedSourceViewPanel.mEditWidget.mEditWidgetContent.mTextCursors.Front.mCursorTextPos;
 								else
 								else
 									embedSource.mCursorIdx = -1;
 									embedSource.mCursorIdx = -1;
 								resolveParams.mEmitEmbeds.Add(embedSource);
 								resolveParams.mEmitEmbeds.Add(embedSource);
@@ -1385,7 +1385,7 @@ namespace IDE.ui
 				{
 				{
 	                if ((useResolveType == .Autocomplete) || (useResolveType == .GetSymbolInfo) || (mIsClang))
 	                if ((useResolveType == .Autocomplete) || (useResolveType == .GetSymbolInfo) || (mIsClang))
 					{
 					{
-						resolveParams.mOverrideCursorPos = (.)mEditWidget.Content.CursorTextPos;
+						resolveParams.mOverrideCursorPos = (.)mEditWidget.Content.mTextCursors.Front.mCursorTextPos;
 						/*if (useResolveType == .Autocomplete)
 						/*if (useResolveType == .Autocomplete)
 							resolveParams.mOverrideCursorPos--;*/
 							resolveParams.mOverrideCursorPos--;*/
 					}
 					}
@@ -2042,7 +2042,7 @@ namespace IDE.ui
 
 
 			ProjectSource projectSource = FilteredProjectSource;
 			ProjectSource projectSource = FilteredProjectSource;
 
 
-			int cursorPos = mEditWidget.mEditWidgetContent.CursorTextPos;
+			int cursorPos = mEditWidget.mEditWidgetContent.mTextCursors.Front.mCursorTextPos;
 
 
 			if ((resolveParams != null) && (resolveParams.mOverrideCursorPos != -1))
 			if ((resolveParams != null) && (resolveParams.mOverrideCursorPos != -1))
 				cursorPos = resolveParams.mOverrideCursorPos;
 				cursorPos = resolveParams.mOverrideCursorPos;
@@ -5254,7 +5254,10 @@ namespace IDE.ui
 
 
 			var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content;
 			var sourceEditWidgetContent = (SourceEditWidgetContent)mEditWidget.Content;
 			if (!sourceEditWidgetContent.CheckReadOnly())
 			if (!sourceEditWidgetContent.CheckReadOnly())
+			{
+				sourceEditWidgetContent.RemoveSecondaryTextCursors();
             	ShowSymbolReferenceHelper(SymbolReferenceHelper.Kind.Rename);
             	ShowSymbolReferenceHelper(SymbolReferenceHelper.Kind.Rename);
+			}
         }
         }
 
 
 		public void FindAllReferences()
 		public void FindAllReferences()

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů