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
        }