( text: string, offset: number, isInner: boolean, isWordChar: (ch: string) => boolean, )
| 58 | } |
| 59 | |
| 60 | function findWordObject( |
| 61 | text: string, |
| 62 | offset: number, |
| 63 | isInner: boolean, |
| 64 | isWordChar: (ch: string) => boolean, |
| 65 | ): TextObjectRange { |
| 66 | // Pre-segment into graphemes for grapheme-safe iteration |
| 67 | const graphemes: Array<{ segment: string; index: number }> = [] |
| 68 | for (const { segment, index } of getGraphemeSegmenter().segment(text)) { |
| 69 | graphemes.push({ segment, index }) |
| 70 | } |
| 71 | |
| 72 | // Find which grapheme index the offset falls in |
| 73 | let graphemeIdx = graphemes.length - 1 |
| 74 | for (let i = 0; i < graphemes.length; i++) { |
| 75 | const g = graphemes[i]! |
| 76 | const nextStart = |
| 77 | i + 1 < graphemes.length ? graphemes[i + 1]!.index : text.length |
| 78 | if (offset >= g.index && offset < nextStart) { |
| 79 | graphemeIdx = i |
| 80 | break |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | const graphemeAt = (idx: number): string => graphemes[idx]?.segment ?? '' |
| 85 | const offsetAt = (idx: number): number => |
| 86 | idx < graphemes.length ? graphemes[idx]!.index : text.length |
| 87 | const isWs = (idx: number): boolean => isVimWhitespace(graphemeAt(idx)) |
| 88 | const isWord = (idx: number): boolean => isWordChar(graphemeAt(idx)) |
| 89 | const isPunct = (idx: number): boolean => isVimPunctuation(graphemeAt(idx)) |
| 90 | |
| 91 | let startIdx = graphemeIdx |
| 92 | let endIdx = graphemeIdx |
| 93 | |
| 94 | if (isWord(graphemeIdx)) { |
| 95 | while (startIdx > 0 && isWord(startIdx - 1)) startIdx-- |
| 96 | while (endIdx < graphemes.length && isWord(endIdx)) endIdx++ |
| 97 | } else if (isWs(graphemeIdx)) { |
| 98 | while (startIdx > 0 && isWs(startIdx - 1)) startIdx-- |
| 99 | while (endIdx < graphemes.length && isWs(endIdx)) endIdx++ |
| 100 | return { start: offsetAt(startIdx), end: offsetAt(endIdx) } |
| 101 | } else if (isPunct(graphemeIdx)) { |
| 102 | while (startIdx > 0 && isPunct(startIdx - 1)) startIdx-- |
| 103 | while (endIdx < graphemes.length && isPunct(endIdx)) endIdx++ |
| 104 | } |
| 105 | |
| 106 | if (!isInner) { |
| 107 | // Include surrounding whitespace |
| 108 | if (endIdx < graphemes.length && isWs(endIdx)) { |
| 109 | while (endIdx < graphemes.length && isWs(endIdx)) endIdx++ |
| 110 | } else if (startIdx > 0 && isWs(startIdx - 1)) { |
| 111 | while (startIdx > 0 && isWs(startIdx - 1)) startIdx-- |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | return { start: offsetAt(startIdx), end: offsetAt(endIdx) } |
| 116 | } |
| 117 |
no test coverage detected