private void SetupJumpToDocumentTabMode(List <JumpWord> jumpWords) { #if MEASUREEXECTIME var watch2_0 = System.Diagnostics.Stopwatch.StartNew(); Stopwatch getCodeWindwowsSW = null; Stopwatch getPrimaryViewSW = null; #endif //var wfs = vsUIShell4.GetDocumentWindowFrames(__WindowFrameTypeFlags.WINDOWFRAMETYPE_Document).GetValueOrDefault(); var wfs = PeasyMotionActivate.Instance.iVsUiShell.GetDocumentWindowFrames().GetValueOrDefault(); var currentWindowFrame = this.vsTextView.GetWindowFrame().GetValueOrDefault(null); Rect emptyRect = new Rect(); SnapshotSpan emptySpan = new SnapshotSpan(); Trace.WriteLine($"GetDocumentWindowFrames returned {wfs.Count} window frames"); int wfi = 0; // there is no easy way to determine document tab coordinates T_T foreach (var wf in wfs) { wf.GetProperty((int)VsFramePropID.Caption, out var oce); string ce = (string)oce; if (currentWindowFrame == wf) { continue; } //GetCodeWindow || GetPrimaryView are f*****g slow! when more than 10 documents to be processed. // IsOnScreen & IsVisible properties are lying, no easy way to optimize View query #if MEASUREEXECTIME if (getCodeWindwowsSW == null) { getCodeWindwowsSW = Stopwatch.StartNew(); } else { getCodeWindwowsSW.Start(); } #endif #if MEASUREEXECTIME getCodeWindwowsSW.Stop(); if (getPrimaryViewSW == null) { getPrimaryViewSW = Stopwatch.StartNew(); } else { getPrimaryViewSW.Start(); } #endif //IVsCodeWindow cw = wf.GetCodeWindow().GetValueOrDefault(null); //IVsTextView wptv = cw?.GetPrimaryView().GetValueOrDefault(null); IVsTextView wptv = wf.GetPrimaryTextView(); #if MEASUREEXECTIME getPrimaryViewSW.Stop(); #endif // VANILLA: //IVsTextView wptv = wf.GetCodeWindow().GetValueOrDefault(null) ? .GetPrimaryView().GetValueOrDefault(null); var distToCurrentDocument = Math.Abs(wfi); var jw = new JumpWord( distanceToCursor: wfi, adornmentBounds: emptyRect, span: emptySpan, text: null, windowFrame: wf, windowPrimaryTextView: wptv, vanillaTabCaption: ce #if DEBUG_LABEL_ALGO , textViewPosDbg: wfi #endif ); jumpWords.Add(jw); } #if MEASUREEXECTIME watch2_0.Stop(); Trace.WriteLine($"PeasyMotion Adornment find visible document tabs: {watch2_0.ElapsedMilliseconds} ms"); Trace.WriteLine($"PeasyMotion get code window total : {getCodeWindwowsSW?.ElapsedMilliseconds} ms"); Trace.WriteLine($"PeasyMotion get primary views total : {getPrimaryViewSW?.ElapsedMilliseconds} ms"); #endif }
private void SetupJumpInsideTextViewMode( List <JumpWord> jumpWords, JumpLabelAssignmentAlgorithm jumpLabelAssignmentAlgorithm, int caretPositionSensivity, string twoCharSearchJumpKeys // null if jumpMode != TwoCharJump ) { #if MEASUREEXECTIME var watch1 = System.Diagnostics.Stopwatch.StartNew(); #endif int currentTextPos = this.view.TextViewLines.FirstVisibleLine.Start; int lastTextPos = this.view.TextViewLines.LastVisibleLine.EndIncludingLineBreak; int currentLineStartTextPos = currentTextPos; // used for line word b/e jump mode int currentLineEndTextPos = currentTextPos; // used for line word b/e jump mode var cursorSnapshotPt = this.view.Caret.Position.BufferPosition; int cursorIndex = this.view.TextViewLines.FirstVisibleLine.Start.Position; bool lineJumpToWordBeginOrEnd_isActive = (jumpMode == JumpMode.LineJumpToWordBegining) || (jumpMode == JumpMode.LineJumpToWordEnding); if ((JumpLabelAssignmentAlgorithm.CaretRelative == jumpLabelAssignmentAlgorithm) || lineJumpToWordBeginOrEnd_isActive) { cursorIndex = cursorSnapshotPt.Position; if ((cursorIndex < currentTextPos) || (cursorIndex > lastTextPos)) { cursorSnapshotPt = this.view.TextSnapshot.GetLineFromPosition(currentTextPos + (lastTextPos - currentTextPos) / 2).Start; cursorIndex = cursorSnapshotPt.Position; } // override text range for line jump mode: if (lineJumpToWordBeginOrEnd_isActive) { var currentLine = this.view.TextSnapshot.GetLineFromPosition(cursorIndex); currentLineStartTextPos = currentTextPos = currentLine.Start; currentLineEndTextPos = lastTextPos = currentLine.EndIncludingLineBreak; } // bin caret to virtual segments accroding to sensivity option, with sensivity=0 does nothing int dc = caretPositionSensivity + 1; cursorIndex = (cursorIndex / dc) * dc + (dc / 2); } // collect words and required properties in visible text //char prevChar = '\0'; var startPoint = this.view.TextViewLines.FirstVisibleLine.Start; var endPoint = this.view.TextViewLines.LastVisibleLine.EndIncludingLineBreak; if (lineJumpToWordBeginOrEnd_isActive) { startPoint = new SnapshotPoint(startPoint.Snapshot, currentLineStartTextPos); endPoint = new SnapshotPoint(endPoint.Snapshot, currentLineEndTextPos); } var snapshot = startPoint.Snapshot; int lastJumpPos = -100; char tmpCh = '\0'; bool prevIsSeparator = Char.IsSeparator(tmpCh); bool prevIsPunctuation = Char.IsPunctuation(tmpCh); bool prevIsLetterOrDigit = Char.IsLetterOrDigit(tmpCh); bool prevIsControl = Char.IsControl(tmpCh); SnapshotPoint currentPoint = new SnapshotPoint(snapshot, startPoint.Position); SnapshotPoint nextPoint = currentPoint; SnapshotPoint prevPoint = currentPoint; int firstPosition = startPoint.Position; int i = firstPosition; int lastPosition = Math.Max(endPoint.Position, 0); if (startPoint.Position == lastPosition) { i = lastPosition + 2; // just skip the loop. noob way :D } // EOL convention reminder | Windows = CR LF \r\n | Unix = LF \n | Mac = CR \r #if DEBUG_LABEL_ALGO int dbgLabelAlgo_TraceStart = i; int dbgLabelAlgo_TraceEnd = lastPosition; #endif int EOL_charCount = 0; // 0 - uninitalized bool EOL_Windows = false; const int MinimumDistanceBetweenLabels = 3; const char CR = '\r'; const char LF = '\n'; bool prevNewLine = false; int jumpPosModifierBase = 0; if (jumpMode == JumpMode.LineBeginingJump) { jumpPosModifierBase = 1; // use next pos as label pos for LineBeginingJump } for (; i <= lastPosition; i++) { var ch = currentPoint.GetChar(); //TraceLine($"before prevpt new SnapshotPoint {i-1} <- pos | {lastPosition} | {i-1}"); prevPoint = new SnapshotPoint(snapshot, Math.Max(i - 1, 0)); //TraceLine($"after prevpt new SnapshotPoint {i-1} <- pos | {lastPosition} | {i-1}"); //TraceLine($"{i} <- pos | {lastPosition} | {i+1}"); var prevChar = prevPoint.GetChar(); //TraceLine($"before new SnapshotPoint {i} <- pos | {lastPosition} | {i+1}"); nextPoint = new SnapshotPoint(snapshot, Math.Min(i + 1, lastPosition - 1)); //TraceLine($"after new SnapshotPoint {i} <- pos | {lastPosition} | {i+1}"); var nextCh = nextPoint.GetChar(); //TraceLine("nextCh"); bool curIsSeparator = Char.IsSeparator(ch); bool curIsPunctuation = Char.IsPunctuation(ch); bool curIsLetterOrDigit = Char.IsLetterOrDigit(ch); bool curIsControl = Char.IsControl(ch); bool nextIsControl = Char.IsControl(nextCh); if ((EOL_charCount == 0) && curIsControl) { if ((prevChar == CR) && (ch == LF)) { EOL_charCount = 2; EOL_Windows = true; } else if ((ch == CR) && (nextCh == LF)) { EOL_charCount = 2; EOL_Windows = true; } else if ((ch == CR) && !prevIsControl && ((nextCh == CR) || !nextIsControl)) { EOL_charCount = 1; } else if ((ch == LF) && !prevIsControl && ((nextCh == LF) || !nextIsControl)) { EOL_charCount = 1; } #if DEBUG_LABEL_ALGO Trace.WriteLine($"EOL chars count = {EOL_charCount}"); #endif } bool newLine = (EOL_Windows && ((prevChar == CR) && (ch == LF))) || (!EOL_Windows && ((ch == LF) || (ch == CR))); int jumpPosModifier = jumpPosModifierBase; bool candidateLabel = false; //TODO: anything faster and simpler ? will regex be faster? maybe symbols // LUT with BITS (IsSep,IsPunct, etc as bits in INT record of LUT?) switch (jumpMode) { case JumpMode.LineJumpToWordBegining: candidateLabel = curIsLetterOrDigit && !prevIsLetterOrDigit; break; case JumpMode.LineJumpToWordEnding: { bool nextIsLetterOrDigit = Char.IsLetterOrDigit(nextCh); candidateLabel = curIsLetterOrDigit && !nextIsLetterOrDigit; } break; case JumpMode.LineBeginingJump: { bool firstLine = i == firstPosition; candidateLabel = (newLine && (i < lastPosition - 1)) || firstLine; jumpPosModifier = firstLine ? 0 : jumpPosModifier; // search till we find non empty char or next EOL int j = i + 1; while (j <= lastPosition) { var pos_j = new SnapshotPoint(snapshot, Math.Min(j, lastPosition - 1)); var ch_j = pos_j.GetChar(); bool EOL_j = (ch_j == CR) && (ch_j == LF); if ((ch_j != ' ') && !EOL_j) { jumpPosModifier = jumpPosModifier + (j - i - 1); break; } else if (EOL_j) { break; } j++; } } break; case JumpMode.TwoCharJump: { candidateLabel = (Char.ToLowerInvariant(ch) == twoCharSearchJumpKeys[0]) && (Char.ToLowerInvariant(nextCh) == twoCharSearchJumpKeys[1]) && (i < lastPosition); } break; default: candidateLabel = (curIsLetterOrDigit && !prevIsLetterOrDigit) || ((prevIsControl || prevNewLine) && newLine) || ((!prevIsControl && !prevNewLine) && !prevIsLetterOrDigit && newLine); //(!curIsControl && nextIsControl) || //((prevChar!= ' ') && !prevIsLetterOrDigit && curIsControl && nextIsControl); break; } bool distanceToPrevLabelAcceptable = (lastJumpPos + jumpPosModifier + MinimumDistanceBetweenLabels) < i; // do not duplicate jump label on CRLF, place on CR only //distanceToPrevLabelAcceptable &= (!EOL_Windows) || (EOL_Windows && (ch == LF)); distanceToPrevLabelAcceptable = distanceToPrevLabelAcceptable || (prevNewLine && newLine); candidateLabel = candidateLabel && ((distanceToPrevLabelAcceptable));// make sure there is a lil bit of space between adornments candidateLabel = candidateLabel && (i < lastPosition); //if (jumpMode == JumpMode.LineJump) { //} #if DEBUG_LABEL_ALGO string cvtChar(char c) { if (ch == '\0') { return(new string('l', 1)); } switch (c) { case '\r': return("<CR>"); case '\n': return("<LF>"); case '\t': return("<TAB>"); case ' ': return("<SPC>"); case '\0': return("NULL"); default: break; } return(new string(c, 1)); }; var dbgCh = cvtChar(ch); var dbgPrevCh = cvtChar(prevChar); var dbgNextCh = cvtChar(nextCh); Trace.WriteLine( $"CAND={candidateLabel,5} POS={i,5} currentChar={dbgCh,5}({(int)ch,5}) isSep={curIsSeparator,5} isPunct={curIsPunctuation,5} " + $"IsLetOrDig={curIsLetterOrDigit,5} isCtrl={curIsControl} ||| " + $" prevChar={dbgPrevCh,5}({(int)prevChar,5}) isSep={prevIsSeparator,5} isPunct={prevIsPunctuation,5} " + $"IsLetOrDig={prevIsLetterOrDigit,5} isCtrl={prevIsControl} ||| " + $"nextChar={dbgNextCh,5}({(int)nextCh,5}) isSep={Char.IsSeparator(nextCh),5} " + $"isPunct={Char.IsPunctuation(nextCh),5} " + $"IsLetOrDig={Char.IsLetterOrDigit(nextCh),5} isCtrl={nextIsControl,5}" + $"(lastJumpPos+MD)<i = {(lastJumpPos + MinimumDistanceBetweenLabels) < i,5} " + $"lastJumpPos = {lastJumpPos}" ); #endif //TraceLine("if (candidateLabel)"); if (candidateLabel) { //TraceLine("INSIDE if (candidateLabel)"); int jumpPosModified = (jumpPosModifier + i) < lastPosition ? (jumpPosModifier + i) : i; //TraceLine(string.Format($"before new SnapshotSpan, {jumpPosModified}, " + $"{jumpPosModified + 1}, {lastPosition}")); SnapshotSpan firstCharSpan = new SnapshotSpan(this.view.TextSnapshot, Span.FromBounds(jumpPosModified, jumpPosModified + 1)); //TraceLine("before GetTextMarkerGeometry"); Geometry geometry = this.view.TextViewLines.GetTextMarkerGeometry(firstCharSpan); if (geometry != null) { var jw = new JumpWord( distanceToCursor: Math.Abs(jumpPosModified - cursorIndex), adornmentBounds: geometry.Bounds, span: firstCharSpan, text: null, windowFrame: null, windowPrimaryTextView: null, vanillaTabCaption: null #if DEBUG_LABEL_ALGO , textViewPosDbg: jumpPosModified #endif ); jumpWords.Add(jw); lastJumpPos = jumpPosModified; #if DEBUG_LABEL_ALGO Trace.WriteLine($"POS={i,5} Adding candidate jump word, lastJumpPos = {lastJumpPos}"); #endif // reset lastJumpPos index if newline encountered, so we wont skip label on line border lastJumpPos = newLine ? -100 : lastJumpPos; } //TraceLine("after GetTextMarkerGeometry"); } prevIsSeparator = curIsSeparator; prevIsPunctuation = curIsPunctuation; prevIsLetterOrDigit = curIsLetterOrDigit; prevIsControl = curIsControl; currentPoint = nextPoint; prevNewLine = newLine; } #if MEASUREEXECTIME watch1.Stop(); Trace.WriteLine($"PeasyMotion Adornment find words: {watch1.ElapsedMilliseconds} ms"); #endif }