* Determines whether a balanced parenthesized expression should be treated as * query grouping in regex mode. This preserves query constructs like: * (file:a or file:b) * while still allowing bare regex atoms like: * (foo|bar)
(input: InputStream, startOffset: number)
| 196 | * (foo|bar) |
| 197 | */ |
| 198 | function isRegexQueryGroupingAt(input: InputStream, startOffset: number): boolean { |
| 199 | const closeOffset = findMatchingCloseParenOffset(input, startOffset); |
| 200 | if (closeOffset === null) { |
| 201 | return false; |
| 202 | } |
| 203 | |
| 204 | let offset = skipWhitespace(input, startOffset + 1); |
| 205 | if (offset >= closeOffset) { |
| 206 | return true; // Empty parens are always grouping syntax |
| 207 | } |
| 208 | |
| 209 | const topLevelTokens: Array<{ start: number; end: number }> = []; |
| 210 | |
| 211 | while (offset < closeOffset) { |
| 212 | const tokenStart = offset; |
| 213 | let depth = 0; |
| 214 | let inQuote = false; |
| 215 | |
| 216 | while (offset < closeOffset) { |
| 217 | const ch = input.peek(offset); |
| 218 | |
| 219 | if (ch === 92 /* backslash */) { |
| 220 | offset += 2; |
| 221 | continue; |
| 222 | } |
| 223 | |
| 224 | if (inQuote) { |
| 225 | offset++; |
| 226 | if (ch === QUOTE) { |
| 227 | inQuote = false; |
| 228 | } |
| 229 | continue; |
| 230 | } |
| 231 | |
| 232 | if (ch === QUOTE) { |
| 233 | inQuote = true; |
| 234 | offset++; |
| 235 | continue; |
| 236 | } |
| 237 | |
| 238 | if (ch === OPEN_PAREN) { |
| 239 | depth++; |
| 240 | offset++; |
| 241 | continue; |
| 242 | } |
| 243 | |
| 244 | if (ch === CLOSE_PAREN) { |
| 245 | if (depth === 0) { |
| 246 | break; |
| 247 | } |
| 248 | depth--; |
| 249 | offset++; |
| 250 | continue; |
| 251 | } |
| 252 | |
| 253 | if (depth === 0 && isWhitespace(ch)) { |
| 254 | break; |
| 255 | } |
no test coverage detected